http://www.ibm.com/developerworks/cn/web/0912_zouxiang_flex_spring/
邹 翔, 信息工程师, IBM
RIA(Rich Internet Application)—— 富 Internet 应用以其良好的客户体验得到越来越广泛的应用,银行,电信等领域已有诸多成功案例。Adobe 的 Flex 是最具有代表性的基于 Flash 的 RIA 技术。通过对 Flex 以及当前流行的 Spring、Hibernate 的 J2EE 开源框架进行集成,使客户既能获得到一流的用户体验,又能获得 J2EE 应用的高稳定性,高可扩展性和可移植性。从某种意义上来说,RIA 是未来 Web 应用的发展方向。作为本文的读者,您应该非常熟悉 J2EE 开发、Spring、Hibernate 框架,并且对 Flex、ActionScript 有一定的了解。通过这篇文章,您能清晰地了解由 Flex、Spring、Hibernate 集成的 RIA 应用框架的技术细节以及怎样使用该框架开发具体的应用。
RIA(Rich Internet Application)—— 富 Internet 应用是一种融入了传统桌面应用系统功能和特性的 Web 应用系统。RIA 结合了桌面应用反应快、交互性强的优点和传统 Web 应用程序易于部署和维护的特性,从而为用户提供了更加健壮,表现力更加丰富的 Web 应用。Adobe Flex 是当前流行的基于 Flash 的 RIA 开源框架,您可以利用 Flex 的基于 XML 的 MXML、ActionScript 以及 SWC 文件(Flex 组件库)创建包含丰富图表、3D 特效、动画、音频以及视频的用户界面,而所有的 Flex 组件将会由 Flex 编译器编译生成 SWF 二进制文件在 Flash Player 中运行。
Spring 是一个轻量级的 J2EE 开源应用程序框架。Spring 的基于两大核心特性控制反转(Inversion of Control,IOC)和面向方面编程(Aspect-Oriented Programming,AOP)的插件式架构降低了应用组件之间的耦合性。借助于 XML 配置文件,Spring 容器可以轻松地帮助您管理应用组件以及它们之间的依赖性,同时 Spring 还提供事务管理框架、DAO 支持、支持主流 O/R Mapping 框架的集成、支持各种标准 J2EE 组件技术的集成(JavaMail/EJB/JMS/Web Service 等)、支持各种视图技术(Web 视图和非 Web 视图)的集成。
Hibernate 是 Java 平台上的一种成熟的、全功能的 O/R Mapping(对象 / 关系映射)开源框架。Hibernate 在分层的 J2EE 架构中位于数据持久化层,它封装了所有数据访问细节,使业务逻辑层可以专注于实现业务逻辑。Hibernate 使用 O/R Mapping 将对象模型中的对象映射到基于 SQL 的关系数据模型中去,通过使用 HQL(Hiberante Query Language)实现 Java 对象的持久化,使得您可以应用面向对象的思维方式操纵数据库。另外,Hibernate O/R Mapping 还提供延迟加载、分布式缓存等高级特性,这样有利于缩短开发周期和降低开发成本。
本文的样例程序是比较流行的在线宠物商店的应用场景,分为两个部分:
- demo_client:Flex 项目,主要由 Flex 实现用户界面的生成和对用户动作的响应处理。
- demo_central:Java Web 项目,主要由 Spring 实现业务逻辑、由 Hibernate 实现数据库的读写访问控制,以及由 BlazeDS 提供 Flex 访问的远程对象接口。在本文中 Spring、BlazeDS 和 Hibernate 所依赖的 Jar 包均拷贝到 demo_central 项目的 WebContent/WEB-INF/lib 目录下部署到 Tomcat 上。
- Eclipse 3.3.1.1
- Web Tools Platform(WTP) for eclipse
- Adobe Flex Builder 3
- Tomcat v6.0
- MySQL v5.1
如何将 Flex 和 Spring 进行集成,使 Flex 前端能够与 Java EE 后端进行通信? Flex 通过远程方法调用和实时通信技术实现异步通信,Flex 的通信协议主要有三种:HttpService、WebService 和 RemoteObject。RomoteObject 协议作为 Flex 提供的最快的通信方式,通过集成 BlazeDS,利用 AMF(Action Message Format)二进制协议使得 Flex 前端能轻松与 Java EE 后端进行数据交互,它是 Flex 集成 Spring 的首选通信协议。
BlazeDS 是 Adobe Live-Cycle Service 的免费开源版本,它使用 AMF 二进制协议通过 AMF 管道构建了 Flex 和 Spring 进行数据通信的桥梁。BlazeDS 可以实现 Flex 对 Java 对象的远程调用。BlazeDS 可以部署运行在大多数 Web 应用服务器上,如 Tomcat、Websphere、JBoss 以及 Weblogic。在本文中我们将 BlazeDS 部署在 Tomcat 上,BlazeDS 所依赖的 jar 包如清单 1 所示:
flex-messaging-common.jar flex-messaging-core.jar flex-messaging-opt.jar flex-messaging-proxy.jar flex-messaging-remoting.jar backport-util-concurrent.jar cfgatewayadapter.jar commons-httpclient-3.0.1.jar commons-codec-1.3.jar commons-logging.jar concurrent.jar xalan.jar |
在 web.xml 部署描述符文件中添加 HttpFlexSession。HttpFlexSession 是 BlazeDS 的一个监听器,负责监听 Flex 远程调用请求。
<listener> <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener> |
现在我们需要引入 Spring BlazeDS Integration(SBI)。SBI 项目是 SpringSource 发布的开源项目,
它是目前比较成熟的用于集成 Flex 和 Spring 的方法。SBI 和 Spring 所依赖的 Jar 包如清单 3 所示:
org.springframework.flex-1.0.0.M2.jar(SBI) spring-beans.jar spring-context.jar spring-context-support.jar spring-core.jar spring-tx.jar spring-webmvc.jar spring.jar |
MessageBroker 是 SBI 的一个组件,它的职责是处理 Flex 远程调用请求。MessageBroker 由 Spring 容器进行管理而不是由 BlazeDS。在 Web.xml 中添加 DispatcherServlet 允许 Spring 自行管理 MessageBroker。
<servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> |
接下来需要在 BlazeDS 的主配置文件 services-config.xml 中添加一个管道定义以支持 DispatcherServlet 对 Flex 请求的映射。
清单 5. 在 services-config.xml 中定义管道
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url= "http://{server.name}:{server.port}/{context.root} /spring/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> |
在 Spring 的配置文件 applicationContext.xml 中,引入 HandlerMapping 通过 MessageBrokerHandlerAdapter 将所有的 Servlet 请求 URL 映射到由 Spring 容器管理的 MessageBroker
,因此需要定义如下的两个 Bean:
清单 6. 定义 HandlerMapping 实现 Servlet 请求映射
<bean class="org.springframework.web.servlet. handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /*=springManagedMessageBroker </value> </property> </bean> <bean class="org.springframework.flex.messaging.servlet. MessageBrokerHandlerAdapter"/> |
最后将定义 MessageBrokerFactoryBean 工厂 Bean 用于创建 MessageBroker,并为您的 Service Bean 提供绑定服务。
清单 7. 定义 MessageBrokerFactoryBean
<bean id="springManagedMessageBroker" class="org.springframework.flex. messaging.MessageBrokerFactoryBean" /> |
在完成上述配置后,启动 Tomcat 将发布 demo_central,然后在浏览器中输入 http://localhost:8080/demo_central/spring/messagebroker/amf 并回车,浏览器就会显示一个空白的 Web 页面,这时我们就成功地对 Flex 和 Spring 进行了集成。
通过集成 Spring 和 Hibernate 3,您能在 Spring 容器的管理下通过 O/R Mapping 进行面向对象的数据持久化。在本文中我们使用 MySQL 数据库,Hibernate 所需要的 Jar 包如清单 8 所示:
hibernate3.jar hibernate-annotations.jar hibernate-commons-annotations.jar mysql-connector-java-5.0.8-bin.jar |
首先定义 Hibernate.properties 创建 Hibernate 参数设置以及数据库连接信息。
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost/pet_store jdbc.username=username jdbc.password=password hibernate.show_sql=true hibernate.format_sql=true hibernate.transaction.factory_class= org.hibernate.transaction.JDBCTransactionFactory hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statements=50 |
创建 HibernateContext.xml 配置文件,对 Hibernate 进行配置并将其纳入 Spring 容器的管理。在这里我们使用基于 Java 5 注解的 Hibernate 注解(Hibernate Annotations)实现对关系型数据库的映射,以代替传统的 hbm.xml 映射文件,这样不仅可以大大简化 Hibernate 映射配置,而且利用 Java 元数据可以提高程序的性能。声明 SessionFactory 为 AnnotationSessionFactoryBean 将注解应用于 Hibernate 实体类中。声明 RequiredAnnotationBeanPostProcessor 作为 BeanPostProcessor 的实现,它将强制 Bean 声明那些被 Spring 注解设置成 Required 的属性,否则将无法通过 XML 验证。最后定义一个类型为 HibernateTransactionManager 的 txManager Bean,并将它注入到 <tx:annotation-driven> 的 transaction-manager 属性,这样就能够在 Spring Bean 中应用 Hibernate 事务注解了。HibernateContext.xml 如清单 10 所示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-lazy-init="true"> <bean id="propertyConfigurer" class="org.springframework.beans.factory. config.PropertyPlaceholderConfigurer"> <property name="location"> <value> WEB-INF/hibernate.properties </value> </property> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3. annotation.AnnotationSessionFactoryBean"> <property name="annotatedClasses"> <list> <value>com.psdemo.core.domain.Client</value> <value>com.psdemo.core.domain.Product</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql"> ${hibernate.show_sql} </prop> <prop key="hibernate.format_sql"> ${hibernate.format_sql} </prop> ...... <prop key="hibernate.connection.driver_class"> ${jdbc.driverClassName} </prop> <prop key="hibernate.connection.url"> ${jdbc.url} </prop> <prop key="hibernate.connection.username"> ${jdbc.username} </prop> <prop key="hibernate.connection.password"> ${jdbc.password} </prop> </props> </property> </bean> <bean class="org.springframework.beans.factory. annotation.RequiredAnnotationBeanPostProcessor"/> <bean id="txManager" class="org.springframework.orm. hibernate3.HibernateTransactionManager"> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> </bean> <tx:annotation-driven transaction-manager="txManager"/> |
清单 11. Hiberante 实体类 Client.java
package com.psdemo.core.domain; import java.io.Serializable; import javax.persistence.*; @Entity @Table(name="client") public class Client implements Serializable{ private static final long serialVersionUID = 2L; @Id @Column(name="username") private String username; @Column(name="password") private String password; public String getUsername(){ return username; } public void setUsername(String username){ this.username = username; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } } |
最后我们需要在 web.xml 部署描述符的 contextConfigLocation 参数中装入 HibernateContext.xml 配置文件。这时我们就成功完成了 Spring 和 Hibernate 的集成,通过 Spring 容器管理 Hibernate,Hibernate 实现数据持久化的管理。
通过 Spring 对 Flex 以及 Hibernate 的集成,现在我们就可以在服务器端声明和定义业务逻辑对象和数据访问对象了。
在 hibernateContext.xml 定义数据访问对象 DAO,注入 SessionFactory。
清单 12. 在 hibernateContext 定义 DAO
<bean id="clientDao" class="com.psdemo.core.dao.hibernate.ClientDaoImpl"> <property name="sessionFactory" ref="sessionFactory"/> </bean> |
public class ClientDaoImpl extends HibernateDaoSupport implements ClientDao { @Override public Client authenticateUser(String userName, String password){ String[] paramNames = new String[]{"userName", "password"}; String[] values = new String[]{userName, password}; List results = this.getHibernateTemplate(). findByNamedParam("from Client as c where c.username=:userName and c.password=:password", paramNames, values); Iterator iter = results.iterator(); if(iter.hasNext()){ return (Client)iter.next(); }else{ return null; } } } |
在 applicationContext.xml 中定义 Service 并开放其 BlazeDS 远程服务,注入 DAO。
清单 14. 定义 Service 并开放其 Flex 远程服务
<bean id="clientservice" class="org.springframework.flex.messaging. remoting.FlexRemotingServiceExporter"> <property name="messageBroker" ref="springManagedMessageBroker"/> <property name="service" ref="clientService"/> </bean> <bean id="clientService" class="com.psdemo.core.services.ClientServiceImpl"> <property name="clientDao"> <ref bean="clientDao"/> </property> </bean> |
Cairngorm 是 Adobe 设计的一个针对 Flex 开发者构建 RIA 应用的轻量级开源框架。Cairngorm 通过设计模式实现了 MVC(Model-View-Controller)微型架构,使得开发人员可以交付具有可重用性、可维护性的中大型 RIA 应用。在本文中我们使用 Cairngorm2.2.1,为了引入 Cairngorm,需要把它的库文件 Cairngorm.swc 拷贝到 Flex 项目的 libs 目录中。
图 1. 将 cairngorm.swc 添加到 Flex 项目库
Cairngorm 是一个事件驱动的框架,其编程模型主要由以下几个组件构成:
值对象(Value Object):值对象是一个映射 Spring 实体类对象的 ActionScript 类对象,它将从 Server 端传输的数据提供给可视化的视图对象。
package com.psdemo.client.model.vo { [Bindable] [RemoteClass(alias="com.psdemo.core.domain.Client")] public class ClientVO { public var username:String; public var password:String; } } |
前端控制器(Front Controller):前端控制器负责绑定由用户触发的事件,并将事件映射到相应的命令。
清单 16. 前端控制器 PMainController.as
public class PMainController extends FrontController { public function PmainController() { addCommand(AuthenticateUserEvent.EVENT_AUTH_USER, AuthenticateUserCommand); addCommand(GetProductsEvent.EVENT_GET_PRODUCTS, GetProductsCommand); } } |
业务代理(Business delegate):利用 Responder 处理由服务器端返回的数据。
public class ClientDelegate { private var responder:IResponder; private var service:Object; public function ClientDelegate(responder:IResponder) { this.service = ServiceLocator.getInstance(). getService("clientservice"); this.responder = responder; } public function authenticateUser(userName:String, password:String) { var call:AsyncToken = service.authenticateUser(userName, password); call.addResponder(responder); } public function addClient(clientVO:ClientVO):void { var call:AsyncToken = service.addClient(clientVO); call.addResponder(responder); } } |
命令(Command):命令负责接收用户事件以执行内部或远程调用,如果是远程调用则需要调用相应的业务代理。如果是内部调用则不需要。
清单 18. AuthenticateUserCommand.as
public class AuthenticateUserCommand implements ICommand, IResponder { private var model:PModelLocator = PModelLocator.getInstance(); public function execute(event:CairngormEvent):void { var authenticateEvent:AuthenticateUserEvent = AuthenticateUserEvent(event); var delegate:ClientDelegate = new ClientDelegate(this); delegate.authenticateUser (authenticateEvent.userName, authenticateEvent.password); } public function result(event:Object):void { var validationPassed:Boolean = (event.result != null); if(validationPassed) { model.MAIN_APP_VIEW = model.APPLICATION_VIEW; } else { model.LoginFailed = true; } } public function fault(event:Object):void { var faultEvt:FaultEvent = event as FaultEvent; Alert.show("ERROR: " + faultEvt.fault); } } |
服务定位器(Service Locator):服务定位器是一个 cairngorm:ServiceLocator 组件,负责定义对 Spring Service 的 RemoteObject 调用的所有端点,在这之前我们需要获取 BlazeDS server 的 AMF 连接 URL 的完整路径,即 http://localhost:8080/demo_central/spring/messagebroker/amf。
<cairngorm:ServiceLocator xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:cairngorm="com.adobe.cairngorm.business.*"> <mx:Script> <![CDATA[ import com.psdemo.client.model.PModelLocator; [Bindable] public var pModelLocator:PModelLocator = PModelLocator.getInstance(); ]]> </mx:Script> <mx:RemoteObject endpoint="{pModelLocator.SERVICE_ENDPOINT}" id="clientservice" destination="clientservice" showBusyCursor="true"> </mx:RemoteObject> <mx:RemoteObject endpoint="{pModelLocator.SERVICE_ENDPOINT}" id="productservice" destination="productservice" showBusyCursor="true"> </mx:RemoteObject> </cairngorm:ServiceLocator> |
模型定位器(Model Locator):模型定位器作为一个单例类负责保存应用程序的状态和从服务器端返回的数据,所有的视图组件都能访问。同时它可以将数据绑定到视图,如果数据被更新,通过数据绑定视图也会自动更新。
[Bindable] public class PModelLocator implements ModelLocator { private static var modelLocator:PModelLocator; public var shoppingCart:ShoppingCart; public var assets:PetStoreAssets; public function PModelLocator() { if(modelLocator != null) { throw new Error("Only one PmodelLocator instance should be instantiated"); } shoppingCart = new ShoppingCart(); assets = new PetStoreAssets(); } public static function getInstance():PModelLocator { if(modelLocator == null) { modelLocator = new PModelLocator(); } return modelLocator; } } |
最后我们需要在主 MXML 文件中设置服务定位器和前端控制器实例,这样就可以对应用中的 CairngormEvent 和 Service 进行监听了。
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" pageTitle="pet store demo" layout="absolute" xmlns:view="com.psdemo.client.view.*" xmlns:services="com.psdemo.client.services.*" xmlns:control="com.psdemo.client.control.*"> <services:Services id="services"/> <control:PMainController id="mainController" /> </mx:Application> |
在进行在线宠物商店的 Flex 视图设计时,Flex Builder 会帮助我们进行可视化界面设计。通过使用 Flex 标准组件接收用户事件,并由 Cairngorm 调度器分发事件到指定的命令以处理用户请求。在用户登录界面 LoginView.mxml 中,ID 值为 txtUserName 和 txtPassword 的 TextInput 组件用于接收用户登录输入,doLogin() 函数负责登录事件的分发。
...... private function doLogin():void { CairngormEventDispatcher.getInstance(). dispatchEvent(new AuthenticateUserEvent( txtUserName.text, txtPassword.text)); } ...... <mx:HBox> <mx:Label text="{resourceManager. getString('Resource', 'username_text')}" width="80"/> <mx:TextInput id="txtUserName" enter="doLogin()" /> </mx:HBox> <mx:HBox> <mx:Label text="{resourceManager. getString('Resource', 'password_text')}" width="80"/> <mx:TextInput id="txtPassword" enter="doLogin()" displayAsPassword="true"/> </mx:HBox> ...... |
宠物商店的登录界面如图 3 所示:
宠物商品列表采用了 TileList 组件以图形列表展示商品,同时实现了拖拽功能添加商品到购物车中,其界面如图 3 所示:
购物车界面如图 4 所示:
本文所描述的系统整体架构图如图 5 所示:
使用 Flex、Spring 和 Hibernate 构建富 Internet 应用程序,充分利用 Flex 强大的接近于桌面应用的 Web 端表现力以及 Java EE 服务器端的优势,让您的 Web 应用真正“动”起来。
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
demo_central | demo_central.zip | 25KB | HTTP |
demo_client | demo_client.zip | 1280KB | HTTP |