2007 年 7 月 27 日
面对市场激烈的竞争,企业需要进行快速响应,以满足市场的需求,复合应用是提高企业业务的灵活性的一个有效的解决方案, 使企业可以充分利用和扩展现在的资源,节约成本, 最大限度地复用组件。 本文主要讨论复合应用的概念以及如何开发基于 Lotus Expeditor 的复合应用,通过一个 Eclipse 实例,展示了具有松散耦合的复合应用的开发过程。
复合应用是面向服务架构(SOA)中的关键要素,它能为企业提供业务灵活性的同时,能够更容易创建新服务或者将现有的IT资产转化为可重用的服务,这些服务可以快速适应不断变化的业务需求。复合应用是由多个相互通讯相互操作的组件组成,而这些组件是松散耦合的,并且在其它复合应用中重复使用。一个组件中的数据发生了变化,可以通过消息广播通知给其它组件,与之线接(Wire)的组件收到消息后进行相应的处理以反映到组件中数据的变化。
本文主要讨论复合应用的概念以及如何开发基于 Expeditor 的复合应用,通过一个 Eclipse 实例,展示了具有松散耦合的复合应用的开发过程。
|
Lotus Expeditor 6.1 是全新的 IBM 智能客户端平台软件,它提供的客户端可以通过服务器进行管理,可以远程部署和维护,它的客户端是多种应用集成的复合界面,支持离线应用等。
可以将部署后的不同的应用组件集成在一个可视的窗体里,而且可以集成后台的业务流程应用。用户不必在不同应用之间进行来回切换,还可以集成工作流在一个复合应用里,复合应用的组件可以是 Swing、文字终端、Visual Basic、Active X、本地或 Web 的 Applet 如 AJAX、PDF、JSP 和 Forms。Lotus Expeditor 还提供了这些组件之间互相通讯,相互操作的能力。不仅可以使得一个应用作为复合桌面的一个组件,而且通过其它新的应用增强现有应用的功能。利用 Lotus Expeditor,Portal 可以是复合应用的一个组成部分,现有的应用可以经过简单的封装,作为复合桌面中的一个组件,最终用户在不影响其操作习惯的基础上,使用集成的复合桌面。这样可以将 Lotus Expeditor 贯穿所有的业务,从而降低最终用户的重新培训的成本,提高生产效率。
Lotus Expeditor 可以将不同数据源的信息集中展现在用户眼前,这些信息可以通过 Portal 应用、Workplace Form 应用、关系数据、Web 服务等形式展现。Lotus Expeditor 为 Portal 提供了一个复合桌面,将 Portal 应用通过响应快速、界面丰富的桌面应用展现出来,而 Portal 的拓扑结构在客户端维护,通过视图的形式展现 Protlet 以及相互间的通讯,而且 Portal 应用还可用于离线模式,即使网络不连通、不稳定的情况下,仍能高效的工作,这特别适用于移动办公的职员和外勤人员。
Lotus Expeditor 可以使 Workplace Forms 应用变成离线应用,表单以及表单数据可存储在桌面客户端,当网络连通后,可以和 Form 服务器进行同步。有些表单,特别是复杂表单,用户可以离线填写,例如在家里填完后,保存在本地,当用户有网络时,再向服务器提交,这可极大的提高办公效率。对于关系数据,Lotus Expeditor 可以使客户不必重写同步算法就可以使后台 ODBC 或 JDBC 数据源,通过 DB2e 的稳健的同步能力,将后台关系数据与客户端数据进行同步。Lotus Expeditor 的客户端服务能力使得从客户端从接受 Web 服务更加容易,从而可以将后端应用如 Siebal 等将 Web 服务接口扩展到可管理客户端环境。另外,Lotus Expeditor 可以利用 WBI 适配器来通过企业服务总线将不同的后端应用扩展到前端。
Lotus Expeditor 可以支持从服务器端的远程部署和维护,这样可大大节约部署和维护的成本,应用管理器能自动完成诸如配置、分发、升级软件、初始化下载等任务,还可以控制组件的访问权限,对管理员和设备用户区别对待。另外,为了提高开发效率,Lotus Expeditor 提供了不同编程模型的选择,开发人员可以开发基于 Eclispe 的用户界面,也可以使用 Servlet 容器,这样开发人员不管是写基于 Eclipse 的应用,还是写 Web 应用,都可以在客户端运行。
Lotus Expeditor 支持离线存储的应用,当数据或者应用在在线时可以下载或同步,当设备离线时,用户仍然可以处理事务,并将数据保存到本地,当网络连通时,数据可按应用的逻辑同步到服务器,与服务器的通讯可以是同步,也可以是异步。
在安全性方面,Lotus Expeditor 提供了诸如加密、凭证存储、锁止桌面等功能来保证客户端的安全性, 本地的凭证存储可以使得通过本地即可验证用户,而不需连通网络,这也使得本地客户端的多个应用通过 Lotus Expeditor 客户端实现单点登录。桌面锁止功能可防止用户从客户端安装非授权的软件,这样,本机上只有服务器管理员安装的软件才能安装于最终用户的机器上。除此之外,Lotus Expeditor 支持多项加密技术(如:关系数据的加密)。
|
Lotus Expeditor 提供了将不同应用进行组装的能力,这些应用虽然具有各自的功能,但通过组装,它们之间可以相互通讯相互操作,进而使得组装后的应用可看作一个单一的应用,从而在每个应用的功能之上增加了组合功能。复合应用有如下的特点:
- 组件之间可以相互通讯;
- 组件之间松耦合,可灵活地用于 SOA 架构;
- 组件或资源可以复用;
- 不同组件可以组装在一个客户端窗体中以获得更加丰富的用户体验;
- 统一的界面风格;
- 事务型组件其信息取自不同的信息源;
- 支持诸如验证、角色和数据保密等安全性;
构成复合应用的相互之间可以通讯和操作的应用程序,我们称为组件,这些组件可以动态进行组装和拆卸,组装后虽然他们可以进行相互通讯相互操作,但它们之间仍旧是松耦合的,一旦一个或多个组件移除,并不影响剩余组件的功能。
不同的技术创建的视窗可以组装于一个窗体内,这些视窗可看着作为组件的外部展现,通过复合应用,可以将这些视窗集成起来,构成复合应用。可以用于组装 Expeditor 复合应用的组件有如下类型:
- SWT 组件;
- AWT 组件;
- 内嵌浏览器组件;
- 支持 JSR168 标准的 Portlet 查看器组件;
- 显示 Servlet 及 JSP 的 Web 容器;
- 可在本地显示远程 Portlet 的 WSRP 查看器;
构成一个复合应用的组件之间需要进行相互通讯,但由于这些组件可以是由不同的开发商提供,这就需要一个标准规范,用于指导开发人员开发出可以用于复合应用的组件。在 Expeditor 中,通过 Property Broker 提供了不同组件之间进行通讯的能力,组件的开发者可以通过 XML 配置文档发布组件的数据或属性(Property)、消息的动作(Action)以及组件间的线接(Wire),Property Broker 能够动态的检测到已经变化的组件的数据,并查找注册过的与该组件相连接或线接的其它组件中的动作,如果找到相应的动作,Property Broker 触发该动作使之执行,这样就完成了两个松耦合组件间的通讯及操作。
在这些相互协作的组件中,提供数据的组件我们称之为提供者,而接受数据并进行动作的组件我们称为接受者,提供者的数据我们称为输出数据,接受者的接受的数据我们称为输入数据,如图 1。
图 1. 数据提供者组件的输出数据和数据接受者组件的输入数据
仅仅定义组件的输出数据或输入数据,两个组件还不能进行相互通讯,只有通过 Property Broker 线接(Wire)起来的组件,才能进行相互通讯相互操作。线接的一端是数据提供者组件中的输出数据,另一端是数据接受者的输入数据,两者的数据类型必须一致,如图 2。
图 2. 两个组件的线接(Wire)
无论是组件发布还是注册,Property Broker 是通过一种 XML 文档来完成的,这一文档的格式是借自 Web 服务描述语言 WSDL(Web Services Description Language)格式,通过这个描述语言,可以描述在复合应用中组件的输入输出数据(property)、消息动作(action)以及类型等。
|
本节我们给出一个由纯粹 Eclipse 组件构成的复合应用,其功能是将当前北京时间换算为世界其它时间,通过此样例,可以详细了解基于 Lotus Expeditor 客户端的复合应用的构建过程。
此样例由两个组件组成,com.ibm.rcp.samples.cityselection
是用户选取世界城市,并显示当前的北京时间(当前系统时间),另外一个组件 com.ibm.rcp.samples.citytime
是显示选中城市的当前当地时间。如图 3:
图 3. 实例界面:世界城市时间与北京时间换算
消息的触发是由 com.ibm.rcp.samples.cityselection
中,用户点击 Send 后,组件 com.ibm.rcp.samples.cityselection
通过 Property Broker 向组件 com.ibm.rcp.samples.citytime
广播所选择的城市名字,组件 com.ibm.rcp.samples.citytime
收到消息事件后,启动一个动作,以计算并显示当前该城市的当地时间。
通过 Property Broker,我们可以声明组件的属性(property)、动作(action)以及线接(wire),以便当松散耦合的某个组件的数据发生变化时,与之线接的组件获得变化的数据并触发相应的动作。 Expeditor 的 Property Broker 提供了一套机制,仅仅通过配置即可将消息动作及属性进行定义,并自动注册到 Property Broker。这种做法的好处是,开发人员可以不必了解 Property Broker 的 API,甚至不必需要 Property Broker 的知识。
Expeditor 客户端 Property Broker 允许有不同类型的组件与之协同工作,当前 Expeditor 有三类动作处理程序:SWT_ACTION
,PORTLET
和 AWT_ACTION
,开发人员可以根据需求实现这三种动作的一种,Broker 通过动作类型将变化了的数据传递给相应的动作处理程序。
如果一个组件没有 UI 界面,可以通过 org.eclipse.core.commands.IHandler
接口来实现自己的动作,这时动作类型为 COMMAND
。如果组件依赖于 Eclipse 的界面,如 SWT 视图,可以用 org.eclipse.core.commands.IHandler
接口来实现动作,并且是 SWT_ACTION
类型,也可以继承 org.eclipse.jface.action.Action
来实现动作。用 SWT_ACTION
类型的好处是当组件所在的透视图隐藏时,复合应用 XML 定义的线接(wire)就会失效。
下面我们讨论如何在组件 com.ibm.rcp.samples.cityselection
中创建一个动作。首先将插件依赖于 com.ibm.rcp.propertybroker
和 com.ibm.rcp.propertybroker.swt
, 然后创建一个 com.ibm.rcp.propertybroker.PropertyBrokerDefinitions
的扩展,这一扩展可自动注册动作以及对应的输入输出数据。Lotus Expeditor Toolkit 提供了一个工具为 Property Broker 定义模板,利用这一导航式工具,可定义出扩展、WSDL 文件及动作的 Java 类文件。在 Property Broker 定义的模板中,可选取模板 SWT Handler,如图 4:
图 4. 扩展点的选取
在模板的导航页面中,可输入动作的包名,动作的处理 Java 类以及类型的命名空间(如图 5),并按 Finish 按钮。
图 5.模板中的数据项
生成器会自动生成类的代码和 WSDL 文件 wsdl/Sample.wsdl。我们需要将自动生成的 WSDL 文件进行重新命名以满足我们的命名规则,我们将文件名命名为 CityName.wsdl,与之关联的扩展点的细节也需相应的改变,如图 6:
图 6. PropertyBrokerDefinition 扩展点中 wsdl 文件的指定
我们可以用文本编辑器更改 CityName.wsdl 的内容,也可以用 Toolkit 带的 Property Broker 的可视化编辑器进行编辑修改。修改后的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <definitions name="OrderDetail_Service" targetNamespace="http://www.ibm.com/wps/c2a" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:portlet="http://www.ibm.com/wps/c2a" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.ibm.com/wps/c2a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <types> <xsd:schema targetNamespace="http://www.ibm.com/wps/c2a"> <xsd:simpleType name="ResultType"> <xsd:restriction base="xsd:string"> </xsd:restriction> </xsd:simpleType> </xsd:schema> </types> <message name="CityName"> <part name="resultFromCitySelection" type="tns:ResultType"/> </message> <portType name="CityName_Service"> <operation name="CityName_Operation"> <output message="tns:CityName"/> </operation> </portType> <binding name="CityNameBinding" type="tns:CityName_Service"> <portlet:binding/> <operation name="CityName_Operation"> <portlet:action name="CityNameAction" type="standard" caption="CityName.GetCityName" description="Get city name" actionNameParameter="ACTION_NAME"/> <output> <portlet:param name="CityName_Text" partname="resultFromCitySelection" boundTo="request-attribute" caption="City Name"/> </output> </operation> </binding> </definitions> |
在这个组件 com.ibm.rcp.samples.cityselection
中, 其输出参数只有一个: CityName_Text
。我们可以用同样的方法,定义组件 com.ibm.rcp.samples.citytime
中的输入输出参数及动作,wsdl 文件的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <definitions name="OrderDetail_Service" targetNamespace="http://www.ibm.com/wps/c2a" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:portlet="http://www.ibm.com/wps/c2a" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.ibm.com/wps/c2a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <types> <xsd:schema targetNamespace="http://www.ibm.com/wps/c2a"> <xsd:simpleType name="ResultType"> <xsd:restriction base="xsd:string"> </xsd:restriction> </xsd:simpleType> </xsd:schema> </types> <message name="CityName"> <part name="resultFromCitySelection" type="tns:ResultType"/> </message> <portType name="CityName_Service"> <operation name="setCityName"> <input message="tns:CityName"/> </operation> </portType> <binding name="CityNameBinding" type="tns:CityName_Service"> <portlet:binding/> <operation name="setCityName"> <portlet:action name="setCityName" type="standard" caption="CityName.GetCityName" description="Get city name" actionNameParameter="ACTION_NAME" activeOnStartup="true"/> <input> <portlet:param name="setCityName" partname="resultFromCitySelection" caption="CityTime"/> </input> </operation> </binding> </definitions> |
在组件 com.ibm.rcp.samples.citytime
中,定义了一个动作 setCityName
以及这个动作的输入参数 CityName
。
利用 Expeditor 的 Property Broker,我们可以定义不同级别的动作实现,如果组件所运行的设备没有 SWT 或 AWT 界面,我们可以用 Eclipse 核心命令的消息处理接口(org.eclipes.core.commands.IHandler
)来实现动作,其中 IHandler
接口的 execute()
方法必须实现。PropertyChangeEvent 可以作为消息的触发器并且可以通过 ExecutionEvent.getTrigger()
获得。 以下是处理数据改变时事件响应的动作处理代码:
public Object execute(ExecutionEvent event) throws ExecutionException { final Object eventTrigger = event.getTrigger(); if (eventTrigger instanceof PropertyChangeEvent){ final PropertyChangeEvent pce = (PropertyChangeEvent)eventTrigger; final PropertyValue value = pce.getPropertyValue(); //TODO implement your handler } return null; } |
我们的样例是基于 Eclipse 的 Expeditor 客户端,有一个视图用以界面显示,这样我们的事件处理代码可以基于 org.eclipse.jface.action.Action
,需要实现的接口函数是 runWithEvent
, 代码如下:
public class setTimeAction extends Action { public void runWithEvent(Event event) { if (event instanceof PropertyChangeEvent) { final PropertyChangeEvent pEvent = (PropertyChangeEvent)event; //System.out.println("Event received"); Thread t = new Thread() { public void run() { PropertyValue value = pEvent.getPropertyValue(); //TODO implement your handler } }; Display display = Display.getDefault(); display.asyncExec(t); } } } |
数据变化的发布可以在任何时候,但一般来讲,是用户通过界面的操作来发起,例如,点击一个按钮或选择列表或表中一行元素等。组件 com.ibm.rcp.samples.cityselection
中,是通过点击 Send 按钮来触发的。Property Broker 的 ChangedProperties()
方法告知 Property Broker,开始向其它组件广播,该组件的参数发生了变化。ChangedProperties 的第二个参数是一个字符串,是与线接配置中的 sourceentityid 一致,我们用当前的 ViewID 来赋值,ViewID 可以用如下的语句获得:
viewID = this.getViewSite().getId() + ":" + this.getViewSite().getSecondaryId(); |
下面是数据发送所用的完整代码,其中 m_sendBtn
是 View 的成员变量,代表 Send 那个按钮。
m_sendBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { PropertyBroker pb = PropertyBrokerFactory.getBroker(); PropertyValue[] values = new PropertyValue[1]; m_BeijingTime.setText( "Beijing Time: "+new Date().toLocaleString() ); try { Property prop = pb.getProperty("http://www.ibm.com/wps/c2a","CityName_Text"); if(prop!=null) { values[0] = PropertyFactory.createPropertyValue(prop, m_City.getText()); pb.changedProperties(values,viewID); } } catch (PropertyBrokerException e) { e.printStackTrace(); } } }); |
没有线接的组件是一个个孤立的组件,只有通过线接的组件,组件之间才能进行通讯,以及进行互操作。为了注册一个线接,需要对扩展点 com.ibm.rcp.propertybroker.PropertyBrokerWire
作扩展,同时定义一个线接,如图 7。
图 7. PropertyBrokerWire 扩展点中数据项的指定
每个线接有如下的属性:
属性名称 | 属性说明 |
---|---|
Type | 类型,当前只支持 PROPERTY_TO_ACTION 线接类型 |
sourcename | 当前线接的发出者名字 |
targetname | 当前线接的接受者的名字 |
name | 线接的名字 |
title | 线接的题目(与线接不影响) |
ownerid | 拥有者组件的 id,通过 Property broker 的 API,可以进行查询 |
odinal | 在 broker 注册表中的排序 |
sourceentityid | 发出者的实体 id |
sourceparam | 线接信息中将要发送给动作的参数 |
targetentityid | 接受者的实体 id |
targetparam | 线接信息中动作接受者的动作参数 |
enable | 当前线接是否可用,注册时自动设为可用 |
uid | 唯一 id |
通常,实体 id 是视图的 id,即视图的第一 id 和第二 id 并在中间加冒号。
如下是我们样例中的线接在 plugin.xml 的描述:
<extension id="com.ibm.rcp.portlet.wire" name="Portlet Wire" point="com.ibm.rcp.propertybroker.PropertyBrokerWire"> <wire sourceentityid="com.ibm.rcp.samples.cityselection.views.CityNameView:null" sourcename="CityName_Text" targetentityid="com.ibm.rcp.samples.citytime.views.CityTimeView:null" targetname="setCityName" type="PROPERTY_TO_ACTION"/> </extension> |
当接受者组件收到 property 更改事件时,通过事件获得对应的线接对象,利用 SWTHelper 的 locateView
方法根据接受者组件的实体 id,查找到对应的 View,并对 View 进行刷新。动作的完整代码如下:
public class setTimeAction extends Action { public void runWithEvent(Event event) { if (event instanceof PropertyChangeEvent) { final PropertyChangeEvent pEvent = (PropertyChangeEvent)event; //System.out.println("Event received"); Thread t = new Thread() { public void run() { PropertyValue value = pEvent.getPropertyValue(); Wire wire = pEvent.getWireDefinition(); CityTimeView view = (CityTimeView)SWTHelper.locateView(wire.getTargetEntityId()); //ColorItem color = new ColorItem(); String result = null; result = (String)value.getValue(); System.out.println("CityName received is "+result); view.setCity(result); } }; Display display = Display.getDefault(); display.asyncExec(t); } } } |
|
Lotus Expeditor 是一个跨系统的客户端平台,在此平台上,可以将不同的应用或组件集成在一个窗体中,而这些组件之间可以相互通讯相互操作,但又是松散耦合的。本文讲述了复合应用的概念,并通过一个 Eclipse 实例,展示了在 Lotus Expeditor 平台上,复合应用的开发过程。开发人员可以根据这一过程,构建出适合自己企业业务需求的复合应用。