EJB 的前世今生
Enterprise Java Beans(簡稱 EJB)的出現是一個足以讓 Sun 引以為驕傲的技術,作為一種 Java Enterprise Edition(簡稱 Java EE)平台上的服務端組件架構模型,EJB 的最初設計目的是為了部署分布式應用程 序。
在 EJB 之前,1991 年 OMG 曾提出了 CORBA 1.1,定義了 IDL 接口定義語言並開發出對象請求代理 ORB 中間 件,並在 1994 年的 12 月完成了 CORBA 2.0,希望通過 IIOP(Internet Inter Object Protocol)來規范和指引不同廠 商的 ORB 之間的通信,增加相應的 API 來實現 Java、C/C++ 及 SmallTalk 等不同語言在不同平台上的程序實現。
當 Java 誕生之後,偉大的 Sun Microsystems 又編寫了 Java Servlet 規范,用來在 Web 服務器上運行程序員們 編寫的 Java 程序,Servlet 這種通過容易管理的 Web 組件,可以產出動態內容,並且加載到 Web 服務器上,來實現與 Web 客戶機的 Request-Response,從而充當聯系前端 Web 請求和後端數據的中間層組件,這樣我們就有了 Web 的三層體 系結構。
這種三層體系架構,將原有的 Client/Server 從服務器端延展服務器、數據中間件、客戶機的表示層,從 而避免了胖客戶端和服務器上的代碼膨脹與冗余。在這時,JavaBeans 的出現就成了水到渠成。JavaBeans 將組件的概念帶 入到 Java 的面向對象編程裡來,並且將軟件重用變得更加容易。它提供了一種簡單便捷的設計方法,將在客戶機或者服務 器端運行的 Java 程序通過可視化的構建器進行編碼,並且使用了設計模式來進行方法和接口的編碼約定;Java 類也將變 量、方法和事件展示出來,通過 JavaBeans 的持久性,將其定制後的狀態加以保持。
到了 1997 年 4 月,Sun 公 司推出了一項對軟件開發改變至深的技術創新,就是 Java 2 Platform Enterprise Edition(即著名的 J2EE),包括了:
Enterprise JavaBeans (EJB) 技術
Java Interface Definition Language (IDL)
Java Message Service (JMS) API
Java Naming and Directory Interface (JNDI)
Java Remote Method Invocation (RMI) 和 Object Serialization
Java Servlet API
Java Transaction API (JTA)
Java Transaction Service (JTS)
JavaServer Pages (JSP) 技術
JDBC 數據庫訪問 API
在 EJB 1.0 的規范中,將各組件與 Java Naming and Directory Interface(簡稱 JNDI)的相關性,在編程中啟動和 停止事務處理時與 Java Transaction API(簡稱 JTA)的相關性做了明確說明與闡述,希望真正實現 write once,run anywhere 的程序員夢想。
之後 EJB 從 1.0 一路走來,增強了與非 java 應用的互操作性,並有了用於實現業務邏 輯的會話 Bean(Session Bean),用於將數據庫中的數據映射為內存中實體對象的實體 Bean(Entity Bean),以及在 EJB2.0 中引入的消息驅動 Bean(MessageDriven Bean)。作為基於 JMS 消息,用於接收客戶端發送的 JMS 數據並處理, MessageDriven Bean 是一個異步無狀態的 Session Bean。這樣當客戶端調用了 MessageDriven Bean 後無需等待就可以返 回到程序中,從而完成客戶的異步處理請求。其實對於 Session Bean 本身來說,既可以是無狀態,也可以是有狀態的。當 客戶端有請求時,Java 容器就會選擇一個 Session Bean 進行服務。而作為域模型對象的 Entity Bean,會將實體 Bean 狀態與數據庫進行同步,保證數據庫與內存中實體對象的統一和完整。
辯證法中,任何事物都有其二面性,EJB 也 不例外。在 Spring 和 Hibernate 等輕量級框架的沖擊下,EJB 2.0 因其的復雜性被眾多的程序員所拋棄。為了在功能易 用與開發復雜之間尋找平衡,EJB 3.0 中提出了許多重要的改變,從而幫助程序員們減輕底層開發的工作量與復雜程度。
EJB 3.0 中兼容並蓄的加入了 Hibernate 的 O/R 映射模型,從原來的 abstract-persistence-schema-based 變成 了現在的 Hibernate-inspired 模式。並且在 EJB QL 中支持了 HQL(屬於 Hibernate 自己的數據庫查詢語言),從而方 便程序員們在處理數據查詢與更新、連接池、事務處理、實體關系等方面更加便捷。
EJB 3.0 的規范中加入了一套 以注釋為基礎的 EJB 編程模型,可以將注釋用於定義 EJB 的業務接口、O/R 映射信息和資源引用信息。對於無狀態的會話 Bean,程序員不用必須制定部署描述符和 home 接口,也不用實現相應的業務接口,只需要一個簡單的 Java 文件並在類層 加上 @Stateless 注釋就可以了,容器會幫助程序員完成。只有消息驅動 Bean(MessageDriven Bean)是必須實現一個業 務接口的 bean。程序員需要用這個接口指出 bean 支持的是哪一種消息系統。例如對於以 JMS 為基礎的消息驅動 bean 來 說,這個接口是 javax.jms.MessageListener。
隨著 EJB3.1 的推出,我們發現從代碼開發到系統部署,EJB3.1 都 讓企業級 Java 開發變得更加輕松。下面我們就一起看看 EJB 3.1 幾項最主要的變化:
Global JNDI names (統一全局 JNDI 命名)
Session beans 的異步調用 (Asynchronous session bean invocation)
No-Interface View(無接口視圖)
Timer-Service ( 調度服務 )
輕量級 EJB(EJB Lite)
Embeddable EJB Containers(嵌入式的 EJB 容器)
簡化的 EJB 打包機制
本文中,我們會簡要介紹 Rational Application Developer V8(簡稱 RAD V8)和 WebSphere Application Server V8 (簡稱WAS V8)對 EJB 3.1 在開發和部署上的新支持。
Rational Application Developer V8 和 WebSphere Application Server V8 對 EJB 3.1 在開發和部署上的新支持
作為具有悠久歷史的 WebSphere Application Server,一直隨著 Java 應用在進行著自我演進。在 WebSphere Application Server V8.0 中,支持了包含 Java EE 6、 OSGi、Web 2.0 & Mobile 在內的廣范編程模型和標准,為程序員提供簡單靈活的安裝維護和管理,增強了與開發工具 Rational Application Developer 和監控工具 ITCAM 的集成。
在對於 EJB 3.1 方面,RAD V8 和 WAS V8 都對 EJB 3.1 所新增的功能做了支持。例如對於輕量級的 Web 應用,開發者可以在 RAD 中采用簡化的 EJB 打包,將開發的 Enterprise Beans 打包到 WAR 包中去。這些類可以放在 WEB-INF/classes 目錄下,或者打成 jar 包放置到 WEB-INF/lib 中去。一個 WAR 包最多只能包含一個 ejb-jar.xml ,該文件可放在 WEB-INF/ejb.jar.xml 下,也可放在 WEB-INF/lib 某 個 ejb-jar 中的 META-INF/ejb-jar.xml 下。這樣就避免了必須將所有 JavaEE 應用程序的 web 前端和 EJB 後端這兩個 模塊 WAR 和 ejb-jar 打包成一個整體。
對於 Global JNDI names(統一全局 JNDI 命名)的支持,可以讓程序員 在 RAD 中使用如下的語法來編寫具有兼容性的全局 JNDI 命名:
java:global[/<app-name>]/<module-name>/<bean-name>[!<fully-qualified-interface- name>]
原來 EJB 的全局 JNDI 命名方式都是供應商各自的實現版本,在 EJB 部署的時候有很多問題。例如,同一個應用程序 中的那些 session beans 在不同供應商的容器中很可能 JNDI 命名並不相同,造成客戶端的調用代碼必須進行調整。除此 之外,支持 EJB3 的廠商允許將本地業務接口配置在全局 JDNI 中,而另一些廠商並不支持這種方式,這也導致了兼容性問 題。EJB 3.1 規范中定義了全局 JNDI 命名方式,采用統一的方式來獲取注冊的 session beans 。
Session beans 的異步調用(Asynchronous session bean invocation)是使用 EJB 開發的程序員最感興趣的新特性之一。在 WAS V8 中 可以應用於所有類型的 session beans 。EJB 3.1 的規范中規定,在容器開始執行某個 bean 實例的調用之前,異步調用 的控制權一定要返回給客戶端。通過這種方式,開發者可以編寫允許客戶端觸發並行處理流程的代碼,使客戶端線程可以並 發處理請求。
WebSphere Application Server V8.0 中支持的 EJB 3.1 特性主要包括:
No-Interface View(無接口視圖)
Session beans 的異步調用 (Asynchronous session bean invocation)
Embeddable EJB Containers(嵌入式的 EJB 容器)
Global JNDI names (統一全局 JNDI 命名)
Singleton session beans
Calendar based timer
Automatic timer creation
Non-persistent timer
Embeddable EJB Container
EJBs in WAR
而 Rational Application Developer for WebSphere Software 作為一個基於 Eclipse 的全功能集成開發環境,一直 保持著與 WAS 同步更新的傳統,RAD 可以幫助開發者快速地進行軟件設計、開發、分析、測試、規劃和部署 Web 服務、 J2EE、Web 應用,以及門戶應用程序等工作。
對於 RAD V8,在"可視地開發 Portlets 和門戶應用程序" 、"組件測試"和"運行時分析"等方面都有不錯的表現。比如在 J2EE 程序需要對運行時進行分析時, RAD 可以從以下幾個方面提供很好的分析數據,如果能夠結合自動化測試腳本,可以非常方便的幫助程序員完成多方面的自 動化測試。
性能:可以顯示應用的流程和執行時間,還包括它的父調用者和子調用;
內存使用情況:可以顯示什麼時候對象被創建,什麼時候它們被垃圾收集以及它們的大小;
代碼范圍:顯示那些代碼被執行了,可以精確到行的級別
線程使用:顯示代碼的線程層次的行為
除了預先設定的分析探針之外,程序員還可以自己定義探針。例如,如果你想增加一些代碼來監視什麼方法被調用,它 的參數以及它內部的算法和計算是怎樣工作的
提供了幾個工具和視圖來幫助理解在運行時分析過程中的數據收集
動態生成的順序圖表:基於產生的 profiling 數據的類型,可以生成順序圖表來描述類之間的互動,對象的互動和線程 的互動。這個視圖還包括一個過濾機制來幫助你專注於你所感興趣的區域
統計數據視圖:這些視圖提供了關於類,實例,包以及代碼區域等等的詳細信息
導航器視圖:這類視圖讓年能夠容易的浏覽各種結果。例子包括 Coverage Navigator,可以讓程序員容易的查看代碼區 域相關的結果
在"可視地開發 Portlets 和門戶應用程序"方面,RAD 包含了一組可視化的門戶開發工具和一個基於各個版 本的 WebSphere Portal 單元測試環境,讓使用者能夠構建和測試單個的 portlet 和整個門戶應用程序。使用向導可以創 建新的 portlet,向導能夠生成符合 J2EE 標准的 portlet 項目結構,同時創建一個完整的 portlet。向導可以創建符合 IBM Portlet API 和 JSR 168 標准的 portlet,這些行業標准是針對 portlet 聚合、個性化、呈現和安全性而制定的。程 序員可以使用 Page Designer 和 JavaServer Faces,設定 portlet 的界面布局,並且使用 Web Diagram Editor 和 Struts 框架來可視化結構和事件流,從而使應用程序更易於維護。
另外,程序員可以使用 Faces 組件將 portlet 和 Java Server Faces 結合起來,用來可視化開發 portlet。使用 Portal Designer,程序員能夠可視地創建和編輯門戶 應用程序,可視地編輯主題和皮膚,控制應用程序的外觀。
下面我們通過一個簡單的計數器應用,介紹如何在 RAD V8 中開發 EJB 3.1 應用,並部署到 WAS V8 上。
麻雀雖小五髒俱全– EJB 3.1 的計數器示例
我們知道, 單態模式(Singleton Pattern)是 Java 設計模式最基礎的創建型模式,在 Singleton 模式中,一個類有且僅有一個實例 ,並且提供一個全局的訪問點。
使用單態模式有許多優點。如控制對資源的使用,通過線程同步來控制資源的並發 訪問;控制實例產生的數量,節省內存,有利於垃圾回收。
在 J2EE 開發中,數據庫連接池的設計一般采用單例模 式,數據庫連接是一種數據庫資源。軟件系統中廣泛使用的數據庫連接池,可以方便地控制打開或者關閉數據庫連接所引起 的效率損耗,並屏蔽不同數據數據庫之間的差異,實現系統對數據庫的低度耦合。
一般來說,數據庫連接池屬於重 量級資源,一個應用中創建一份即可,既節省了資源又方便管理。因此,數據庫連接池采用單例模式進行設計會是一個比較 好的選擇。
在本文示例中,我們使用 RAD V8 開發一個名為 EJBCounterSampleEE6 的 EJB 3.1 計數器小項目。
該項目中主要包含了具有 EJB 3.1 Singleton Bean(同時具有接口類和實現類)和 JPA 2.0 實體類的 EJB 3.1 項 目(稱為 EJBCounterSampleEE6);具有 Java ServerPages(JSP)頁面和實用程序 Java 類的 Web 項目 (EJBCounterWebEE6);Web 片段項目(EJBCounterWebFragEE6);以及具有 EJB 和 Web 項目的 Java EE 6 應用程序 (EJBCounterEAR6);並使用 Derby 創建一個簡單的數據庫 EJB3SampleDB 用來保存計數器數據。
在本文的開發環 境中,我們使用了 IBM Rational Application Developer for WebSphere Software V8.0.2 和 WebSphere Application Server V8.0 beta。
圖 1. 本文中所涉及的項目明細
在我們創建 EJBCounterSampleEE6 的 EJB 項目後,我們將為 SingletonCounterBean.java 類創建所需類和接口。
從程序清單 1 和清單 2 中,我們可以看到 ,在 @Singleton 注釋下,我們加入 @Interceptors ( Audit.class ),並根據 SingletonCounterBean 需要創建 Audit.java Java 類;然後創建本地計數器和遠程計數器的接口:LocalCounter 與 RemoteCounter(參見程序清單 3)。
清單 1. SingletonCounterBean.java
// This program may be used, executed, copied, modified and distributed // without royalty for the purpose of developing, using, marketing, or distributing. package com.ibm.example.websphere.ejb3sample.counter; import javax.ejb.Singleton; import javax.interceptor.Interceptors; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Singleton @Interceptors ( Audit.class ) public class SingletonCounterBean implements LocalCounter, RemoteCounter { // 需要我們創建二個簡單的接口用於本地和遠程計數 private static final String CounterDBKey = "PRIMARYKEY"; // Use container managed persistence - inject the EntityManager @PersistenceContext (unitName="Counter") private EntityManager em; public int increment() { int result = 0; try { JPACounterEntity counter = em.find(JPACounterEntity.class, CounterDBKey); // 在程序清單 4 中,創建 JPACounterEntity if ( counter == null ) { counter = new JPACounterEntity(); counter.setPrimaryKey(CounterDBKey); em.persist( counter ); } counter.setValue( counter.getValue() + 1 ); em.flush(); em.clear(); result = counter.getValue(); } catch (Throwable t) { System.out.println("SingletonCounterBean:increment - caught unexpected exception: " + t); t.printStackTrace(); } return result; } public int getTheValue() { int result = 0; try { JPACounterEntity counter = em.find(JPACounterEntity.class, CounterDBKey); if ( counter == null ) { counter = new JPACounterEntity(); em.persist( counter ); em.flush(); } em.clear(); result = counter.getValue(); } catch (Throwable t) { System.out.println("SingletonCounterBean:increment - caught unexpected exception: " + t); t.printStackTrace(); } return result; } }
清單 2. Audit.java
// This program may be used, executed, copied, modified and distributed // without royalty for the purpose of developing, using, marketing, or distributing. package com.ibm.example.websphere.ejb3sample.counter; import java.io.Serializable; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; public class Audit implements Serializable { private static final long serialVersionUID = 4267181799103606230L; @AroundInvoke public Object methodChecker (InvocationContext ic) throws Exception { System.out.println("Audit:methodChecker - About to execute method: " + ic.getMethod()); Object result = ic.proceed(); return result; } }
在清單 1 中,我們定義了二個方法 increment 和 getTheValue 用於計數器的累積和獲取返回值。
清單 3. RemoteCounter.java - 創建遠程計數器的接口,本地計數器接口與之類似
// This program may be used, executed, copied, modified and distributed // without royalty for the purpose of developing, using, marketing, or distributing. package com.ibm.example.websphere.ejb3sample.counter; import javax.ejb.Remote; @Remote public interface RemoteCounter { public int increment(); public int getTheValue(); }
之後我們創建實體類 JPACounterEntity,並在 RAD 的"企業資源管理器"定義好數據源。在本示例中 ,我們在 Derby 中創建數據庫 EJB3SampleDB 以保存計數器數據。因此,我們在"WebSphere 部署"編輯器中, 選擇 Derby JDBC 提供程序(XA),然後在上面選中的 JDBC 提供程序中定義的數據源。
清單 4. JPACounterEntity.java - 創建實體類
// This program may be used, executed, copied, modified and distributed // without royalty for the purpose of developing, using, marketing, or distributing. package com.ibm.example.websphere.ejb3sample.counter; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="EJB3COUNTERTABLE") public class JPACounterEntity { @Id private String primarykey = "PRIMARYKEY"; private int value = 0; public void setValue( int newValue ) { System.out.println ("JPACounterEntity:setValue = " + newValue); value = newValue; } public int getValue() { System.out.println ("JPACounterEntity:getValue = " + value); return value; } public void setPrimaryKey( String newKey ) { System.out.println ("JPACounterEntity:setPrimaryKey = '" + newKey + "'"); primarykey = newKey; } public String getPrimaryKey() { System.out.println ("JPACounterEntity:getPrimaryKey = '" + primarykey + "'"); return primarykey; } }
之後,我們完成 Web 項目 EJBCounterWebEE6 和 Web 片段項目 EJBCounterWebFragEE6 的創建,並編寫 Web 頁面 EJBCount.jsp 和 Servlet EJBCount.java 的代碼(相關代碼可在代碼下載中獲得,由於篇幅所限,本文不做展開介 紹)。
RAD V8 中提供了很好的方式與 WAS V8 集成,用於跟蹤和調試 EJB 代碼。基於本文示例,我們在 RAD 中使用概要分析 方式運行 EJBCounterEAR6。隨後在彈出的通用測試客戶機界面中輸入 WAS V8 的用戶名和密碼(如圖 2、圖 3 所示)。
圖 2. 在 WAS 服務器上采用概要分析方式運行 EJBCounterEAR6
圖 3. 在通用測試客戶機中輸入 WAS 的用戶名和 密碼
隨 後,右鍵點擊 EJBCounterSampleEE6 中的會話 Bean"SingletonCounterBean",選擇進行希望進行分析的本地或 遠程計數器。
圖 4. 選擇在服務器上運行的本地或遠程計數器
當選擇好 WAS 服務器上運行的 EJB 程序後 ,我們可以在通用測試客戶機上自動同步看到 EJB Bean 狀態和當前調用信息。
本示例中我們選擇了 RemoteCounter 運行,因此在 EJB Bean 和持久性單元中我們看到了"RemoteCounter"和"Counter"的 信息。並可以通過點擊方式查看並執行 EJB 程序。如圖 5 中所示,當點擊 RemoteCounter 中的方法 increment 時,通用 測試客戶機可以直接將調用結果返回到界面上,方便程序員進行查看和調試。
圖 5. 在通用測試客戶機上查看遠程 計數器
我們通過浏覽器也同樣可以查看到相應計數器數值的變化,如圖 6 所示。
圖 6. 在浏覽器中查看 EJB 3.1 遠程計 數器
在 RAD V8 中,如果我們啟用代碼覆蓋率分析功能,RAD 可以幫助程序員查看處於"概要分析"狀態的程序在經過手 工或自動測試後相應的被測代碼覆蓋率。
如圖 7 所示,在本文示例的簡單計數器程序中,當通用測試客戶機未調用 getTheValue 方法時,SingletonCounterBean.java 的代碼行覆蓋率為 33%,其中 getTheValue 的覆蓋率為 0%;
圖 7. 在計數器程序中,未調用方法 getTheValue 時的代碼覆蓋率
當在通用測試客戶機中調用 getTheValue 後 ,我們可以從圖 8 中明顯看到 SingletonCounterBean.java 的代碼行覆蓋率上升為 56%,其中 getTheValue 的行覆蓋率 為 50%;點擊 SingletonCounterBean.java,打開代碼後我們發現,未被測試到的代碼主要集中在 counter == null 和 catch 部分的異常處理;由此我們可以看到,一味追求測試的代碼覆蓋率其實並沒有太多意義,無論是代碼行覆蓋還是函數 覆蓋,其最終的意義在於保證被測代碼的質量;因此,通過自動測試來觀察並記錄隨著測試用例的執行代碼覆蓋率的變化是 十分必要的。在適當的時候,使用 RAD 這類工具提供的代碼覆蓋率或執行時間分析等功能能夠有效保證軟件的質量。
圖 8. 當調用方法 getTheValue 之後的代碼覆蓋率變化
小結
在本文中,我們回溯了 EJB 發展的歷史與 EJB 3.1 一些新的特性,並簡單介紹了 IBM Rational Application Developer for WebSphere Software V8 和 WebSphere Application Server V8 對於 EJB 開發的支持。通過 一個名為 EJBCounterSampleEE6 的 EJB 3.1 計數器小程序,我們了解了在 RAD V8 中如何新建一個 EJB 3.1 項目,最終 發布至 WAS V8 中的場景。通過 RAD 的概要分析和結合 WAS 的通用測試客戶機,我們看到了如何使用 RAD+WAS 來對應 EJB 3.1 進行實時分析與代碼覆蓋率檢查,從而真正實現 Develop in Rational and Run on WebSphere。
代碼下載
本文所涉及代碼全部來自於 IBM Rational Application Developer for WebSphere Software V8 中的示例代碼,有興 趣的讀者可以在安裝過樣例庫的 RAD V8 中查看,也可以通過 RAD 在線幫助得到相應源代碼,並根據 IBM Rational Application Developer 官方課程逐步進行操作。