將Flex集成到Java EE應用程序的最佳實踐

火星人 @ 2014-03-09 , reply:0


傳統的 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 前端的遠程調用請求時,BlazeDS 通過 FlexFactory 創建 FactoryInstance 對象,並傳入請求的 Service ID.在這個應用程序中,被創建的 FactoryInstance 實際對象是 SpringFactoryInstance;

3、FactoryInstance 的 lookup() 方法被調用,在 SpringFactoryInstance 中,查找 Spring 容器,然後,通過 Bean 的 ID 查找 Bean,最終,FlexService 介面的實例被返回.

注意到 destination 的 id 並沒有寫死在代碼中,而是通過以下語句獲得的:

清單 11. 獲取 destination 的 ID

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

Property 的 SOURCE 屬性由 BlazeDS 讀取 XML 配置文件獲得:

清單 12. 配置 destination 的 id

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

如果您沒有使用 Spring 框架,也不要緊,只需修改 FactoryInstance 的 lookup() 方法.例如,對於一個 EJB 來說,lookup() 方法應該通過 JNDI 查找返回遠程介面.無論應用程序結構如何,我們的最終目標是向 BlazeDS 返回一個 FlexService 的實例對象.

開發 Flex 客戶端

安裝 Flex Builder 3,可以在 Adobe 的官方網站獲得 30 天免費試用版.然後,打開 Flex Builder 3,創建一個新的 Flex Project,命名為 EmployeeMgmt-Flex:

圖 3. 新建 Flex 工程 - 第一步

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

圖 4. 新建 Flex 工程 - 第二步

因此,需要填入 EmployeeMgmt-Server 項目的 web 根目錄,該目錄下要存在 /WEB-INF/flex/.點擊「Validate Configuration」驗證配置文件是否正確,只有通過驗證后,才能繼續.默認地,Flex Builder 將會把生成的 Flash 文件放到 EmployeeMgmt-Server 項目的 web/EmployeeMgmt-Flex-debug 目錄下.

一個 Flex Project 的目錄結構如下:

圖 5. Flex 工程的目錄結構

用 Flex Builder 做出漂亮的用戶界面非常容易.Flex Builder 提供了一個可視化的編輯器,通過簡單的拖拽,一個毫無經驗的開發人員也能夠設計出漂亮的布局.如果熟悉一點 XML 的知識,編輯 MXML 也並非難事.我們設計的 Employee Management 系統界面的最終效果如下:

圖 6. 用 Flex Builder 的可視化編輯器設計界面

本文不打算討論如何編寫 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 進行調用雖然簡單,但存在不少問題:,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 腳本:

清單 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 方法:

清單 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 模板完成的.

有了 Java 類到 ActionScript 的自動轉換,我們在編寫 ActionScript 時,就能享受到編譯器檢查和 ActionScript 類方法的自動提示了:

圖 8. Flex Builder 的代碼自動補全

唯一的缺憾是通過反射讀取 FlexService 介面時,我們失去了方法的參數名稱,因此,FlexServiceRO 的方法參數名只能變成 arg1,arg2 …… 等,要讀取 FlexService 介面的方法參數名,只能通過解析 Java 源代碼實現.

現在,Java EE 後端開發團隊和 Flex 前端開發團隊只需協商定義好 FlexService 介面,然後,利用 Java2ActionScript,Flex 團隊就得到了強類型的 FlexServiceRO 類,而 Java EE 團隊則只需集中精力實現 FlexService 介面.

在開發的前期,甚至可以用硬編碼的 FlexService 的實現類.每當 FlexService 變動時,只需再次運行 Ant 腳本,就可以獲得最新的 FlexServiceRO 類.這樣,兩個團隊都可以立刻開始工作,僅需要通過 FlexService 介面就可以完美地協同開發.

下載

描述 名字 大小 下載方法
Java EE 工程源碼 EmployeeMgmt-Server.zip 8.6 MB HTTP
Flex 工程源碼 EmployeeMgmt-Flex.zip 17.9 KB HTTP
Java2ActionScript 工程源碼 Java2ActionScript.zip 1.3 MB HTTP





[火星人 via ] 將Flex集成到Java EE應用程序的最佳實踐已經有213次圍觀

http://www.coctec.com/docs/java/show-post-61101.html