假定我们已经拥有了一个管理雇员信息的 Java EE 应用,名为 EmployeeMgmt-Server,结构如 图 1 所示:
图 1. Java EE 工程结构

这 是一个典型的 Java EE 应用,使用了流行的 Spring 框架。为了简化数据库操作,我们使用了内存数据库 HSQLDB。对这个简单的应用,省略了 DAO,直接在 Fa?ade 中通过 Spring 的 JdbcTemplate 操作数据库。最后,EmployeeMgmt 应用通过 Servlet 和 JSP 页面为用户提供前端界面:

该界面为传统的 HTML 页面,用户每次点击某个链接都需要刷新页面。由于 Employee Management 系统更接近于传统的桌面应用程序,因此,用 Flex 重新编写界面会带来更好的用户体验。

如 何将 Flex 集成至该 Java EE 应用呢?现在,我们希望用 Flex 替换掉原有的 Servlet 和 JSP 页面,就需要让 Flex 和 Java EE 后端通信。Flex 支持多种远程调用方式,包括 HTTP,Web Services 和 AMF。不过,针对 Java EE 开发的服务器端应用,可以通过集成 BlazeDS,充分利用 AMF 协议并能轻易与 Flex 前端交换数据,这种方式是 Java EE 应用程序集成 Flex 的首选。

BlazeDS 是 Adobe LifeCycle Data Services 的开源版本,遵循 LGPL v3 授权,可以免费使用。BlazeDS 为 Flex 提供了基于 AMF 二进制协议的远程调用支持,其作用相当于 Java 的 RMI。有了 BlazeDS,通过简单的配置,一个 Java 接口就可以作为服务暴露给 Flex,供其远程调用。

尽管现有的 EmployeeMgmt 应用程序已经有了 Fa?ade 接口,但这个接口是暴露给 Servlet 使用的,最好能再为 Flex 定义另一个接口 FlexService,并隐藏 Java 语言的特定对象(如 清单 1 所示):

public interface FlexService {     Employee createEmployee(String name, String title, boolean gender, Date birth);     void deleteEmployee(String id);     Employee[] queryByName(String name);     Employee[] queryAll(); }

 

现在,Java EE 后端与 Flex 前端的接口已经定义好了,要完成 Java EE 后端的接口实现类非常容易,利用 Spring 强大的依赖注入功能,可以通过几行简单的代码完成:

public class FlexServiceImpl implements FlexService {     private static final Employee[] EMPTY_EMPLOYEE_ARRAY = new Employee[0];     private Facade facade;       public void setFacade(Facade facade) {         this.facade = facade;     }       public Employee createEmployee(String name, String title, boolean gender,         Date birth) {         return facade.createEmployee(name, title, gender, birth);     }       public void deleteEmployee(String id) {         facade.deleteEmployee(id);     }       public Employee[] queryAll() {         return facade.queryAll().toArray(EMPTY_EMPLOYEE_ARRAY);     }       public Employee[] queryByName(String name) {         return facade.queryByName(name).toArray(EMPTY_EMPLOYEE_ARRAY);     } }

 

然后,我们将 BlazeDS 所需的 jar 包放至 de style=”margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;”</WEB-INF/lib/de<。BlazeDS 需要如下的 jar:

backport-util-concurrent.jar commons-httpclient.jar commons-logging.jar flex-messaging-common.jar flex-messaging-core.jar flex-messaging-proxy.jar flex-messaging-remoting.jar

 

在 web.xml 中添加 HttpFlexSession 和 Servlet 映射。HttpFlexSession 是 BlazeDS 提供的一个 Listener,负责监听 Flex 远程调用请求,并进行一些初始化设置:

<listener>     <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener>

 

MessageBrokerServlet 是真正处理 Flex 远程调用请求的 Servlet,我们需要将其映射到指定的 URL:

<servlet>     <servlet-name>messageBroker</servlet-name>     <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>     <init-param>         <param-name>services.configuration.file</param-name>         <param-value>/WEB-INF/flex/services-config.xml</param-value>     </init-param>     <load-on-startup>0</load-on-startup> </servlet>   <servlet-mapping>     <servlet-name>messageBroker</servlet-name>     <url-pattern>/messagebroker/*</url-pattern> </servlet-mapping>

 

BlazeDS 所需的所有配置文件均放在 /WEB-INF/flex/ 目录下。BlazeDS 将读取 services-config.xml 配置文件,该配置文件又引用了 remoting-config.xml、proxy-config.xml 和 messaging-config.xml 这 3 个配置文件,所以,一共需要 4 个配置文件。

由于 BlazeDS 需要将 Java 接口 FlexService 暴露给 Flex 前端,因此,我们在配置文件 remoting-config.xml 中将 FlexService 接口声明为一个服务:

<destination id="flexService">     <properties>         <source>org.expressme.employee.mgmt.flex.FlexServiceImpl</source>         <scope>application</scope>     </properties> </destination>

 

服务名称通过 destination 的 id 属性指定,Flex 前端通过该服务名称来进行远程调用。scope 指定为 application,表示该对象是一个全局对象。

然 而,按照默认的声明,BlazeDS 会去实例化 FlexService 对象。对于一个 Java EE 应用来说,通常这些服务对象都是被容器管理的(例如,Spring 容器或 EJB 容器),更合适的方法是查找该服务对象而非直接实例化。因此,需要告诉 BlazeDS 通过 Factory 来查找指定的 FlexService 对象,修改配置如下:

<destination id="flexService">     <properties>         <factory>flexFactory</factory>         <source>flexService</source>         <scope>application</scope>     </properties> </destination>

 

现 在,Flex 如何才能通过 BlazeDS 调用 FlexService 接口呢?由于 FlexService 对象已经被 Spring 管理,因此,我们需要编写一个 FlexFactory 告诉 BlazeDS 如何找到 Spring 管理的 FlexService 的实例。flexFactory 在 services-config.xml 中指定:

<factories>     <factory id="flexFactory"/> </factories>

 

FlexFactoryImpl 实现了 FlexFactory 接口,该接口完成两件事情:

  1. 创建 FactoryInstance 对象;
  2. 通过 FactoryInstance 对象查找我们需要的 FlexService。

因此,需要一个 FactoryInstance 的实现类,我们编写一个 SpringFactoryInstance,以便从 Spring 的容器中查找 FlexService:

class SpringFactoryInstance extends FactoryInstance {     private Log log = LogFactory.getLog(getClass());       SpringFactoryInstance(FlexFactory factory, String id, ConfigMap properties) {         super(factory, id, properties);     }       public Object lookup() {         ApplicationContext appContext = WebApplicationContextUtils.                 getRequiredWebApplicationContext(                     FlexContext.getServletConfig().getServletContext()         );         String beanName = getSource();         try {             log.info("Lookup bean from Spring ApplicationContext: " + beanName);             return appContext.getBean(beanName);         }         catch (NoSuchBeanDefinitionException nex) {             ...         }         catch (BeansException bex) {             ...         }         catch (Exception ex) {             ...         }     } }

 

FlexFactoryImpl 负责实例化 SpringFactoryInstance 并通过 SpringFactoryInstance 的 de style=”margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;”<lookup()de< 方法查找 FlexService 接口对象:

public class FlexFactoryImpl implements FlexFactory {     private Log log = LogFactory.getLog(getClass());       public FactoryInstance createFactoryInstance(String id, ConfigMap properties) {         log.info("Create FactoryInstance.");         SpringFactoryInstance instance = new SpringFactoryInstance(this, id, properties);         instance.setSource(properties.getPropertyAsString(SOURCE, instance.getId()));         return instance;     }       public Object lookup(FactoryInstance instanceInfo) {         log.info("Lookup service object.");         return instanceInfo.lookup();     }       public void initialize(String id, ConfigMap configMap) {     } }

 

以下是 BlazeDS 查找 FlexService 接口的过程:

  1. BlazeDS 将首先创建 FlexFactory 的实例—— FlexFactoryImpl;
  2. 当 接收到 Flex 前端的远程调用请求时,BlazeDS 通过 FlexFactory 创建 FactoryInstance 对象,并传入请求的 Service ID。在这个应用程序中,被创建的 FactoryInstance 实际对象是 SpringFactoryInstance;
  3. FactoryInstance 的 lookup() 方法被调用,在 SpringFactoryInstance 中,首先查找 Spring 容器,然后,通过 Bean 的 ID 查找 Bean,最终,FlexService 接口的实例被返回。

注意到 destination 的 id 并没有写死在代码中,而是通过以下语句获得的:

properties.getPropertyAsString(SOURCE, instance.getId())

 

Property 的 SOURCE 属性由 BlazeDS 读取 XML 配置文件获得:

<destination id="flexService">     <properties>         <factory>flexFactory</factory>         <source>flexService</source>         <scope>application</scope>     </properties> </destination>

 

如 果您没有使用 Spring 框架,也不要紧,只需修改 FactoryInstance 的 lookup() 方法。例如,对于一个 EJB 来说,lookup() 方法应该通过 JNDI 查找返回远程接口。无论应用程序结构如何,我们的最终目标是向 BlazeDS 返回一个 FlexService 的实例对象。

首先安装 Flex Builder 3,可以在 Adobe 的官方网站获得 30 天免费试用版。然后,打开 Flex Builder 3,创建一个新的 Flex Project,命名为 EmployeeMgmt-Flex:

Flex Project 需要指定 Server 端的配置文件地址:

因此,需要填入 EmployeeMgmt-Server 项目的 web 根目录,该目录下必须要存在 de style=”margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;”</WEB-INF/flex/de<。点击“Validate Configuration”验证配置文件是否正确,只有通过验证后,才能继续。默认地,Flex Builder 将会把生成的 Flash 文件放到 EmployeeMgmt-Server 项目的 de style=”margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;”<web/EmployeeMgmt-Flex-debugde< 目录下。

一个 Flex Project 的目录结构如下:

用 Flex Builder 做出漂亮的用户界面非常容易。Flex Builder 提供了一个可视化的编辑器,通过简单的拖拽,一个毫无经验的开发人员也能够设计出漂亮的布局。如果熟悉一点 XML 的知识,编辑 MXML 也并非难事。我们设计的 Employee Management 系统界面的最终效果如下:

本文不打算讨论如何编写 Flex 界面,而是把重点放在如何实现远程调用。

为了能在 Flex 中实现远程调用,我们需要定义一个 RemoteObject 对象。可以通过 ActionScript 编码创建该对象,也可以直接在 MXML 中定义一个 RemoteObject 对象,并列出其所有的方法:
清单 13. 定义 flexServiceRO

<mx:RemoteObject id="flexServiceRO" destination="flexService">     <mx:method name="queryAll" result="handleQueryAll(result : ResultEvent)"/> </mx:RemoteObject>

 

现在,就可以调用这个名为 flexServiceRO 的 RemoteObject 对象的方法了:
清单 14. 调用 FlexServiceRO.queryAll()

 flexServiceRO.queryAll(function(result : ResultEvent) {     var employees = result.result as Array;  });

 

运行该 Flex Application,雇员信息已经被正确获取了:
图 7. 在浏览器中运行 Flex application

通 过 RemoteObject 进行调用虽然简单,但存在不少问题:首先,RemoteObject 是一个 Dynamic Class,Flex Builder 的编译器无法替我们检查参数类型和参数个数,这样,在编写 ActionScript 代码时极易出错。此外,接口变动时(这种情况常常发生),需要重新修改 RemoteObject 的定义。此外,Flex 团队需要一份随时修订的完整的 FlexService 接口文档才能工作。

因 此,最好能使用强类型的 RemoteObject 接口,让 Flex Builder 的编译器及早发现错误。这个强类型的 RemoteObject 最好能通过 Java EE 应用的 FlexService 接口自动生成,这样,就无需再维护 RemoteObject 的定义。

为 了能完成自动生成 RemoteObject 对象,我编写了一个 Java2ActionScript 的 Ant 任务来自动转换 FlexService 接口以及相关的所有 JavaBean。JavaInterface2RemoteObjectTask 完成一个 Java 接口对象到 RemoteObject 对象的转换。使用如下的 Ant 脚本:

<taskdef name="genactionscript" classname="org.expressme.ant.JavaBean2ActionScriptTask">     <classpath refid="build-classpath" /> </taskdef> <taskdef name="genremoteobject"     classname="org.expressme.ant.JavaInterface2RemoteObjectTask">     <classpath refid="build-classpath" /> </taskdef> <genactionscript     packageName="org.expressme.employee.mgmt"     includes="Employee"     orderByName="true"     encoding="UTF-8"     outputDir="${gen.dir}" /> <genremoteobject     interfaceClass="org.expressme.employee.mgmt.flex.FlexService"     encoding="UTF-8"     outputDir="${gen.dir}"     destination="flexService" />

 

转换后的 FlexServiceRO 类拥有 Java 接口对应的所有方法,每个方法均为强类型签名,并添加额外的两个可选的函数处理 result 和 fault 事件。例如,queryByName 方法:

public function queryByName(arg1 : String, result : Function = null,     fault : Function = null) : void {     var op : AbstractOperation = ro.getOperation("queryByName");     if (result!=null) {         op.addEventListener(ResultEvent.RESULT, result);     }     if (fault!=null) {         op.addEventListener(FaultEvent.FAULT, fault);     }     var f : Function = function() : void {         op.removeEventListener(ResultEvent.RESULT, f);         op.removeEventListener(FaultEvent.FAULT, f);         if (result!=null) {             op.removeEventListener(ResultEvent.RESULT, result);         }         if (fault!=null) {             op.addEventListener(FaultEvent.FAULT, fault);         }     }     op.addEventListener(ResultEvent.RESULT, f);     op.addEventListener(FaultEvent.FAULT, f);     op.send(arg1); }

 

转 换 Java 接口是通过 Interface.as 和 InterfaceMethod.as 两个模板文件完成的,此外,所有在 Java EE 后端和 Flex 之间传递的 JavaBean 对象也通过 JavaBean2ActionScriptTask 自动转换成对应的 ActionScript 类,这是通过 Bean.as 模板完成的。

有了 Java 类到 ActionScript 的自动转换,我们在编写 ActionScript 时,就能享受到编译器检查和 ActionScript 类方法的自动提示了:

唯一的缺憾是通过反射读取 FlexService 接口时,我们失去了方法的参数名称,因此,FlexServiceRO 的方法参数名只能变成 arg1,arg2 …… 等,要读取 FlexService 接口的方法参数名,只能通过解析 Java 源代码实现。

现 在,Java EE 后端开发团队和 Flex 前端开发团队只需协商定义好 FlexService 接口,然后,利用 Java2ActionScript,Flex 团队就得到了强类型的 FlexServiceRO 类,而 Java EE 团队则只需集中精力实现 FlexService 接口。

在 开发的前期,甚至可以用硬编码的 FlexService 的实现类。每当 FlexService 变动时,只需再次运行 Ant 脚本,就可以获得最新的 FlexServiceRO 类。这样,两个团队都可以立刻开始工作,仅需要通过 FlexService 接口就可以完美地协同开发。

原文引自IBM-DEVELOP:    Thanks for 廖 雪峰 (askxuefeng@gmail.com), 软件工程师, HP

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注