将 Flex 集成到 Java EE 应用程序的最佳实践
开发环境
本文的开发环境为 Windows 7 Ultimate,Eclipse 3.4,Flex Builder 3(从 参考资源 获得下载链接)。Java EE 服务器使用 Resin 3.2,当然,您也可以使用 Tomcat 等其他 Java EE 服务器。@H_301_14@
现有的 Java EE 应用
假定我们已经拥有了一个管理雇员信息的 Java EE 应用,名为 EmployeeMgmt-Server,结构如 图 1 所示:@H_301_14@
图 1. Java EE 工程结构
@H_301_14@
这是一个典型的 Java EE 应用,使用了流行的 Spring 框架。为了简化数据库操作,我们使用了内存数据库 HsqlDB。对这个简单的应用,省略了 DAO,直接在 Façade 中通过 Spring 的 JdbcTemplate 操作数据库。最后,EmployeeMgmt 应用通过 Servlet 和 JSP 页面为用户提供前端界面:@H_301_14@
图 2. EmployeeMgmt Web 界面
@H_301_14@
该界面为传统的 HTML 页面,用户每次点击某个链接都需要刷新页面。由于 Employee Management 系统更接近于传统的桌面应用程序,因此,用 Flex 重新编写界面会带来更好的用户体验。@H_301_14@
集成 BlazeDS
如何将 Flex 集成至该 Java EE 应用呢?现在,我们希望用 Flex 替换掉原有的 Servlet 和 JSP 页面,就需要让 Flex 和 Java EE 后端通信。Flex 支持多种远程调用方式,包括 HTTP,Web Services 和 AMF。不过,针对 Java EE 开发的服务器端应用,可以通过集成 BlazeDS,充分利用 AMF 协议并能轻易与 Flex 前端交换数据,这种方式是 Java EE 应用程序集成 Flex 的首选。@H_301_14@
BlazeDS 是 Adobe LifeCycle Data Services 的开源版本,遵循 LGPL v3 授权,可以免费使用。BlazeDS 为 Flex 提供了基于 AMF 二进制协议的远程调用支持,其作用相当于 Java 的 RMI。有了 BlazeDS,通过简单的配置,一个 Java 接口就可以作为服务暴露给 Flex,供其远程调用。@H_301_14@
尽管现有的 EmployeeMgmt 应用程序已经有了 Façade 接口,但这个接口是暴露给 Servlet 使用的,最好能再为 Flex 定义另一个接口 FlexService,并隐藏 Java 语言的特定对象(如 清单 1 所示):@H_301_14@
清单 1. FlexService interface
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 强大的依赖注入功能,可以通过几行简单的代码完成:@H_301_14@
清单 2. FlexServiceImpl class
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,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 包放至 /WEB-INF/lib/
。BlazeDS 需要如下的 jar:@H_301_14@
清单 3. 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 远程调用请求,并进行一些初始化设置:@H_301_14@
清单 4. 定义 Flex Listener
<listener> <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener>
MessageBrokerServlet 是真正处理 Flex 远程调用请求的 Servlet,我们需要将其映射到指定的 URL:@H_301_14@
清单 5. 定义 Flex servlet
<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 个配置文件。@H_301_14@
由于 BlazeDS 需要将 Java 接口 FlexService 暴露给 Flex 前端,因此,我们在配置文件 remoting-config.xml 中将 FlexService 接口声明为一个服务:@H_301_14@
清单 6. 定义 flexService 服务
<destination id="flexService"> <properties> <source>org.expressme.employee.mgmt.flex.FlexServiceImpl</source> <scope>application</scope> </properties> </destination>
服务名称通过 destination 的 id 属性指定,Flex 前端通过该服务名称来进行远程调用。scope 指定为 application,表示该对象是一个全局对象。@H_301_14@
然而,按照默认的声明,BlazeDS 会去实例化 FlexService 对象。对于一个 Java EE 应用来说,通常这些服务对象都是被容器管理的(例如,Spring 容器或 EJB 容器),更合适的方法是查找该服务对象而非直接实例化。因此,需要告诉 BlazeDS 通过 Factory 来查找指定的 FlexService 对象,修改配置如下:@H_301_14@
清单 7. 通过 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 中指定:@H_301_14@
清单 8. 定义 flexFactory
<factories> <factory id="flexFactory" class="org.expressme.employee.mgmt.flex.FlexFactoryImpl"/> </factories>
FlexFactoryImpl 实现了 FlexFactory 接口,该接口完成两件事情:@H_301_14@
- 创建 FactoryInstance 对象;
- 通过 FactoryInstance 对象查找我们需要的 FlexService。
因此,需要一个 FactoryInstance 的实现类,我们编写一个 SpringFactoryInstance,以便从 Spring 的容器中查找 FlexService:@H_301_14@
清单 9. SpringFactoryInstance class
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 的 lookup()
方法查找 FlexService 接口对象:@H_301_14@
清单 10. FlexFactoryImpl class
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,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 接口的过程:@H_301_14@
- BlazeDS 将首先创建 FlexFactory 的实例—— FlexFactoryImpl;
- 当接收到 Flex 前端的远程调用请求时,BlazeDS 通过 FlexFactory 创建 FactoryInstance 对象,并传入请求的 Service ID。在这个应用程序中,被创建的 FactoryInstance 实际对象是 SpringFactoryInstance;
- FactoryInstance 的 lookup() 方法被调用,在 SpringFactoryInstance 中,首先查找 Spring 容器,然后,通过 Bean 的 ID 查找 Bean,最终,FlexService 接口的实例被返回。
注意到 destination 的 id 并没有写死在代码中,而是通过以下语句获得的:@H_301_14@
清单 11. 获取 destination 的 ID
properties.getPropertyAsString(SOURCE,instance.getId())
Property 的 SOURCE 属性由 BlazeDS 读取 XML 配置文件获得:@H_301_14@
清单 12. 配置 destination 的 id
开发 Flex 客户端首先安装 Flex Builder 3,可以在 Adobe 的官方网站获得 30 天免费试用版。然后,打开 Flex Builder 3,创建一个新的 Flex Project,命名为 EmployeeMgmt-Flex:@H_301_14@
图 3. 新建 Flex 工程 - 第一步
@H_301_14@
Flex Project 需要指定 Server 端的配置文件地址:@H_301_14@
图 4. 新建 Flex 工程 - 第二步
@H_301_14@
因此,需要填入 EmployeeMgmt-Server 项目的 web 根目录,该目录下必须要存在
/WEB-INF/flex/
。点击“Validate Configuration”验证配置文件是否正确,只有通过验证后,才能继续。默认地,Flex Builder 将会把生成的 Flash 文件放到 EmployeeMgmt-Server 项目的web/EmployeeMgmt-Flex-debug
目录下。@H_301_14@一个 Flex Project 的目录结构如下:@H_301_14@
图 5. Flex 工程的目录结构
@H_301_14@
用 Flex Builder 做出漂亮的用户界面非常容易。Flex Builder 提供了一个可视化的编辑器,通过简单的拖拽,一个毫无经验的开发人员也能够设计出漂亮的布局。如果熟悉一点 XML 的知识,编辑 MXML 也并非难事。我们设计的 Employee Management 系统界面的最终效果如下:@H_301_14@
图 6. 用 Flex Builder 的可视化编辑器设计界面
@H_301_14@
本文不打算讨论如何编写 Flex 界面,而是把重点放在如何实现远程调用。@H_301_14@
为了能在 Flex 中实现远程调用,我们需要定义一个 RemoteObject 对象。可以通过 ActionScript 编码创建该对象,也可以直接在 MXML 中定义一个 RemoteObject 对象,并列出其所有的方法:@H_301_14@
清单 13. 定义 flexServiceRO
<mx:RemoteObject id="flexServiceRO" destination="flexService"> <mx:method name="queryAll" result="handleQueryAll(result : ResultEvent)"/> </mx:RemoteObject>现在,就可以调用这个名为 flexServiceRO 的 RemoteObject 对象的方法了:@H_301_14@
清单 14. 调用 FlexServiceRO.queryAll()
flexServiceRO.queryAll(function(result : ResultEvent) { var employees = result.result as Array; });运行该 Flex Application,雇员信息已经被正确获取了:@H_301_14@
图 7. 在浏览器中运行 Flex application
@H_301_14@
回页首@H_301_14@增强 RemoteObject 对象
通过 RemoteObject 进行调用虽然简单,但存在不少问题:首先,RemoteObject 是一个 Dynamic Class,Flex Builder 的编译器无法替我们检查参数类型和参数个数,这样,在编写 ActionScript 代码时极易出错。此外,接口变动时(这种情况常常发生),需要重新修改 RemoteObject 的定义。此外,Flex 团队需要一份随时修订的完整的 FlexService 接口文档才能工作。@H_301_14@
因此,最好能使用强类型的 RemoteObject 接口,让 Flex Builder 的编译器及早发现错误。这个强类型的 RemoteObject 最好能通过 Java EE 应用的 FlexService 接口自动生成,这样,就无需再维护 RemoteObject 的定义。@H_301_14@
为了能完成自动生成 RemoteObject 对象,我编写了一个 Java2ActionScript 的 Ant 任务来自动转换 FlexService 接口以及相关的所有 JavaBean。JavaInterface2RemoteObjectTask 完成一个 Java 接口对象到 RemoteObject 对象的转换。使用如下的 Ant 脚本:@H_301_14@
清单 15. 生成 ActionScript class 的 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 方法:@H_301_14@
清单 16. 自动生成的 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 模板完成的。@H_301_14@
有了 Java 类到 ActionScript 的自动转换,我们在编写 ActionScript 时,就能享受到编译器检查和 ActionScript 类方法的自动提示了:@H_301_14@
图 8. Flex Builder 的代码自动补全
@H_301_14@
唯一的缺憾是通过反射读取 FlexService 接口时,我们失去了方法的参数名称,因此,FlexServiceRO 的方法参数名只能变成 arg1,arg2 …… 等,要读取 FlexService 接口的方法参数名,只能通过解析 Java 源代码实现。@H_301_14@
现在,Java EE 后端开发团队和 Flex 前端开发团队只需协商定义好 FlexService 接口,然后,利用 Java2ActionScript,Flex 团队就得到了强类型的 FlexServiceRO 类,而 Java EE 团队则只需集中精力实现 FlexService 接口。@H_301_14@
在开发的前期,甚至可以用硬编码的 FlexService 的实现类。每当 FlexService 变动时,只需再次运行 Ant 脚本,就可以获得最新的 FlexServiceRO 类。这样,两个团队都可以立刻开始工作,仅需要通过 FlexService 接口就可以完美地协同开发。@H_301_14@
回页首@H_301_14@下载
描述 名字 大小 Java EE 工程源码 EmployeeMgmt-Server.zip 8.6 MB Flex 工程源码 EmployeeMgmt-Flex.zip 17.9 KB Java2ActionScript 工程源码 @L_404_11@ 1.3 MB 参考资料
学习
- “Adobe Flex 参考资料”:查看 Adobe Flex 的文档集。
- “Flex 开发入门”(developerWorks,2009 年 1 月):本文介绍 Flex 开发的基础知识:包括如何搭建开发环境,如何调试,以及如何建立和部署简单的 Flex 项目。
- “集成 Flex 与 Ajax 应用程序”(developerWorks,2008 年 7 月):本文将介绍 Adobe Flex Ajax Bridge (FABridge),这是让您可以采用轻松而一致的方法集成 Ajax 与 Flex 内容的代码库。
- “用 Flex 开发 Google Map 应用程序”(developerWorks,2009 年 3 月):介绍如何用 Google Maps API for Flash 来开发基于 Flash 的地图应用程序。
- developerWorks Java 技术专区:这里有数百篇关于 Java 编程的文章。
获得产品和技术
- Adobe Flex:访问 Flex 产品页面。
- Adobe BlazeDS:访问 Adobe BlazeDS 项目站点。
- 下载:Eclipss 3.4。
- 下载:Flex Builder。
讨论
- 访问 developerWorks blog 并加入 @L_502_22@。
条评论
不错的实践! 不过 要运行例子,需要改一下FlexServiceRO 的端点参数,否则不能通过blazeDS,传递数据!
远程对象构造函数:
public function FlexServiceRO() }
ro = new RemoteObject();
ro.destination = "flexService";
ro.endpoint = "http://localhost:8080/\u201CcontextPath\u201D/messagebroker/amf";
ro.concurrency = "last";
ro.makeObjectsBindable = true;
ro.showBusyCursor = false;
{@H_301_14@
请 登录 或 注册 后发表评论。@H_301_14@
@H_301_14@
注意:评论中不支持 HTML 语法@H_301_14@