本文摘自http://www.ibm.com/developerworks/cn/java/j-lo-jeeflex/#listing1
将 Flex 集成到 Java EE 应用程序的最佳实践
传统的 Java EE 应用程序通常使用某种 MVC 框架(例如,Struts)作为前端用户界面,随着 Flex 的兴起,基于 RIA 的客户端能够给用户带来更酷的界面,更短的响应时间,以及更接近于桌面应用程序的体验。本文将讲述如何将 Flex 集成至一个现有的 Java EE 应用程序中,以及如何应用最佳实践高效率地并行开发 Java EE 和 Flex。
开发环境
本文的开发环境为 Windows 7 Ultimate,Eclipse 3.4,Flex Builder 3(从 参考资源 获得下载链接)。Java EE 服务器使用 Resin 3.2,当然,您也可以使用 Tomcat 等其他 Java EE 服务器。
现有的 Java EE 应用
假定我们已经拥有了一个管理雇员信息的 Java EE 应用,名为 EmployeeMgmt-Server,结构如 图 1 所示:
图 1. Java EE 工程结构
这是一个典型的 Java EE 应用,使用了流行的 Spring 框架。为了简化数据库操作,我们使用了内存数据库 HSQLDB。对这个简单的应用,省略了 DAO,直接在 Façade 中通过 Spring 的 JdbcTemplate 操作数据库。最后,EmployeeMgmt 应用通过 Servlet 和 JSP 页面为用户提供前端界面:
图 2. EmployeeMgmt Web 界面
该界面为传统的 HTML 页面,用户每次点击某个链接都需要刷新页面。由于 Employee Management 系统更接近于传统的桌面应用程序,因此,用 Flex 重新编写界面会带来更好的用户体验。
集成 BlazeDS
如何将 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 所示):
清单 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 强大的依赖注入功能,可以通过几行简单的代码完成:
清单 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, 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 包放至 /WEB-INF/lib/。BlazeDS 需要如下的 jar:
清单 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 远程调用请求,并进行一些初始化设置:
清单 4. 定义 Flex Listener
<listener> <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener>
|
MessageBrokerServlet 是真正处理 Flex 远程调用请求的 Servlet,我们需要将其映射到指定的 URL:
清单 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 个配置文件。
由于 BlazeDS 需要将 Java 接口 FlexService 暴露给 Flex 前端,因此,我们在配置文件 remoting-config.xml 中将 FlexService 接口声明为一个服务:
清单 6. 定义 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 对象,修改配置如下:
清单 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 中指定:
清单 8. 定义 flexFactory
<factories> <factory id="flexFactory" class="org.expressme.employee.mgmt.flex.FlexFactoryImpl"/> </factories>
|
FlexFactoryImpl 实现了 FlexFactory 接口,该接口完成两件事情:
1 创建 FactoryInstance 对象;
2 通过 FactoryInstance 对象查找我们需要的 FlexService。
因此,需要一个 FactoryInstance 的实现类,我们编写一个 SpringFactoryInstance,以便从 Spring 的容器中查找 FlexService:
清单 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 接口对象:
清单 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, 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