Apache Geronimo 通信基礎 —— 開發、部署和測試(上)
Apache Geronimo 包含頂尖的消息傳遞實現,還有世界級的 Enterprise JavaBeans(EJB)實現,從而使您可在自己的應用程序中使用 消息驅動 bean(MDB)。這也就意味著只要具備恰當的資源適配器,其他組件就可以使用消息傳遞與您的應用程序交互。本系列教程詳細 敘述了 Java™ Platform, Enterprise Edition(Java EE)應用程序的創建,在這個應用程序中,用戶、管理員甚至其他應用程序 都可利用 MDB 進行交互,方法是向 Java Apache Mail Enterprise Server(也稱為 Apache James)發送電子郵件,而 Apache James 隨 後又使用 J2EE Connector Architecture(JCA)資源適配器反過來與 Geronimo 通信。
開始之前
本系列教程面向希望學習如何使用各種 Java EE 組件(包括 MDB 和 JCA 資源適配器)構建集成化解決方案的 Java EE 程序員。本教程假設您熟悉基本的 Java 和 Java EE 概念,例如 EJB、Java Message Service(JMS)、MDB 和 Unified Modeling Language(UML)圖。
關於本系列
在這個共分三部分的系列教程中,您將構建一個示例應用程序,通過這種方式了解如何將 不同的 Java EE 組件集成在一起,來開發復雜的應用程序。
您可 下載 本文的示例應用程序,它示范了 Apache James 中電子郵 件的數據是如何通過 JCA 資源適配器、MDB、EJB 流向 Apache Geronimo 應用服務器的。
本期是系列教程的第 1 部分,介紹了如何開發 MDB、實體 bean 和容器管理的持久性(CMP),以及如何在 Apache Geronimo 中部署 和測試這些組件。
第 2 部分將解釋如何創建電子郵件應用程序(mailet 和 matcher)並將它們部署在 Apache James 電子郵件服務器中。
第 3 部分將整個應用程序聯系在一起。您將學習為 Apache James 電子郵件服務器(它通過 MDB 與 James 和 Geronimo 交互)開發 、部署和測試 JCA 資源適配器。
關於本教程
本教程是共分三部分的系列教程中的第 1 部分,集中關注開發、部署和測試一個集成化 Java EE 應用程序的各種組件。該應用程序示 范了 Apache James 電子郵件服務器中一封電子郵件的數據是如何通過 JCA 資源適配器和 MDB 流向 Apache Geronimo 應用服務器的。
系統需求
為完成本教程的學習,您需要具備以下工具:
Apache Geronimo —— Apache 提供的 Java EE 應用服務器
Apache James 2.2 —— 基於 Java 的 Simple Mail Transfer Protocol(SMTP)、Post Office Protocol V3(POP3)和 Network News Transfer Protocol(NNTP)新聞服務器
Apache Derby 數據庫 —— 開放源碼、輕量級數據庫,嵌入在 Geronimo 內,因此無需單獨安裝
Sun Microsystems 公司提供的 Java 1.4.2
示例源文件
在本教程的 下載 部分可獲得 geronimo.mdb.part1source.zip 文件,其中包含源代碼、EJB JAR 和本教程的描述符文件。下面詳細列 出了 .zip 文件的組成部分:
- dds(包含描述符 xml 文件)
- deploy(po-ejb.jar)
- lib(examples.jar 和 tester.jar)
- src(mdb 和實體 ejb 的 Java 文件)
- deploy.cmd
- undeploy.cmd
- runtester.cmd
EJB 程序包
下面列出了 po-ejb.jar 文件的組成部分(部署在 Geronimo 中):
描述符文件:
- META-INF/ejb-jar.xml
- META-INF/openejb-jar.xml
類文件:
- examples/po/ejb/*.class
- examples/po/bean/*.class
- examples/po/mdb/*.class
基本應用程序
本教程的目的是介紹在 Apache Geronimo 上編寫可輕松與其他基於 Java 的技術集成的 Java EE 應用程序的概念和基礎知識。您將通 過使用 Java EE 組件開發一個示例應用程序來學會這些知識。
在本教程的場景示例和編碼練習中,您將使用 Foo, Inc.,這是一家虛擬企業。盡管 Foo, Inc. 是虛擬的,但業務場景和示例都是真 實的。
Foo, Inc. 的遺留采購訂單流程
讓我們來看一下 Foo, Inc 的遺留采購訂單(PO)流程。這是一個手工流程,員工填寫采購申請單,將申請單提交給采購部門進行授權 、批准,最終將 PO 提交給廠商。
這看上去是一個效率極其低下、耗費時間的流程,可以通過軟件進行自動化。因此,Foo, Inc. 決定購買一套新的 PO 系統(假設是一 種基於數據庫的產品),實現將內部采購申請單提交給廠商的電子化通信。
新的采購訂單流程
新的 PO 流程允許一名員工發送電子郵件請求,由 PO 經理授權進行電子化批准,從而自動創建 PO 並將其提交給廠商。
新流程詳述如下:
員工通過電子郵件將采購請求發送給采購部門。
采購請求電子郵件由一個 Java EE 應用程序處理,該應用程序確保請求者是經過授權的。若授權流程成功完成,電子郵件將轉發到指 定文件夾中,以便進一步處理。
Java EE 應用程序的另外一個組件輪詢包含經過授權的請求的電子郵件文件夾,並在 PO 系統中創建一個新采購訂單。
隨後可在 PO 系統中檢索采購訂單並將訂單發送給廠商。
應用程序設計
既然已經了解了 Foo, Inc 的新采購訂單流程,那麼下一步就是設計我們的 Java EE 示例應用程序,來實現新流程。
示例應用程序需求
您將從為示例應用程序收集需求開始。
應用程序需要處理來自員工的傳入采購請求電子郵件,並將其移動到可由采購部門訪問的特定文件夾。
隨後,您的應用程序將讀取請求,並檢查該員工是否確實來自 Foo, Inc.。
一旦經過授權,即創建一份新的采購訂單,以便提交給廠商。
這樣,您確定了三個需求,現在您需要將其建模為用例。
用例分析
用例分析捕獲整體需求,並描述示例應用程序的單個特性和功能性。它由一組用例和參與者構成。
用例
用例代表業務需求。在我們的例子中,也就是我們剛剛為 Foo, Inc. 的新采購訂單流程確定出的需求:
發送采購請求電子郵件。需求:Foo, Inc. 的員工向采購部門發送帶有采購請求的電子郵件。
處理采購請求電子郵件。需求:應用程序通過授權和將采購請求電子郵件移動到其他文件夾中來處理這些電子郵件。
檢查采購請求電子郵件。需求:應用程序將連續檢查等候處理的新采購請求電子郵件。
在 PO 系統中添加采購訂單。需求:應用程序將在 PO 系統中添加新采購訂單。
參與者
參與者是在應用程序或系統中承擔一個角色的實體。參與者可以是人、組織或應用程序。以下列表包含了您的參與者:
Foo, Inc. 的員工
電子郵件應用程序
J2EE 應用程序
參與者與應用程序交互,因而也顯示為與 圖 1 中所示的用例交互。
圖 1. 參與者和用例
現在您就可以為您的應用程序確定實現這些用例所必需的不同 Java EE 組件了。
組件
確定出您的應用程序中的用例之後,下一步就是將這些需求/用例建模為 Java EE 組件。圖 2 中的組件圖顯示了各種組件及其交互作 用。讓我們從詳細了解應用程序流程和各應用程序組件在更高層面中的角色開始。
應用程序流程
Foo, Inc. 的員工發送一封采購請求電子郵件。這種電子郵件將由您的示例應用程序處理(組件:mailet 和 matcher),它將授權采 購請求,並將請求轉發到屬於采購部門的特定電子郵件文件夾中。您的應用程序(組件:JCA 適配器)將不斷輪詢新采購請求。一旦接收 到新的采購請求電子郵件,示例應用程序(組件:JCA 資源適配器和 MDB)將調用消息偵聽器來異步處理采購請求。應用程序(組件:MDB 和 EJB)隨後在 PO 系統(數據庫)中創建一份新的采購訂單。
下面列舉了組件及其相應用例。您將分三部分構建這個應用程序(在本系列的三篇教程中):
實體 bean —— CMP:在 PO 系統中添加采購訂單(第 1 部分)。
MDB:在 PO 系統中添加采購訂單(第 1 部分)。
電子郵件客戶機應用程序:發送采購請求電子郵件(第 2 部分)。
電子郵件應用程序:處理采購請求電子郵件(第 2 部分)。
JCA 資源適配器:檢查采購請求電子郵件(第 3 部分)。
圖 2. 組件圖
通常,可以在完全不了解將用於部署的應用服務器的情況下設計和開發 Java EE 應用程序。
在開發、部署和測試過程中,您要在 Apache Geronimo 和 Apache James 中部署示例應用程序。您將使用 Apache Derby 作為 PO 系 統數據庫。(在本教程開始處的 系統需求 部分中可找到下載這些產品的鏈接。)
既然您已經了解了應用程序流程和示例應用程序的設計,接下來就該動手開發第 1 部分的應用程序組件了。
第 1 部分的應用程序開發
在這裡,您將為用例在 PO 系統中添加采購訂單 開發組件。在組件設計中,您將使用 MDB 異步接收來自 JCA 資源適配器和實體 EJB (CMP)的采購請求,在 PO 系統中添加新采購訂單。
您將調用您的 MDB PurchaseOrderMDB 和實體 bean(CMP)PurchaseOrderEJB。PurchaseOrderMDB 接收到一個采購請求之後,它將調 用 PurchaseOrderEJB 在數據中插入一個新的采購訂單。
下面詳細介紹了在 Geronimo 內對 MDB 和 CMP 實體 bean 的實現和部署。
實體 bean(PurchaseOrderEJB)
您要根據 EJB 2.1 規范將 PurchaseOrderEJB 作為 CMP 實體 bean 實現。實體 bean 代表持久數據,在本例中,它表示作為 PO 系統 的數據庫內的一行。由於您使用的是 CMP,Java EE 容器會讀/寫數據庫中的數據。
在示例應用程序中,PurchaseOrderEJB 與數據庫表 PURCHASEORDER 交互,定義參見 清單 1。
清單 1. 表定義
TABLE PURCHASEORDER (
PURCHASEORDERNUM VARCHAR(30) PRIMARY KEY,
ITEM VARCHAR(30) NOT NULL,
DESCRIPTION VARCHAR(255),
UNITPRICE INTEGER,
QUANTITY INTEGER,
REQUESTOREMAIL VARCHAR(30) NOT NULL
)
接下來您將實現處理采購訂單的 EJB。
實現 PurchaseOrderEJB
EJB 2.1 規范要求實體 bean 必須具有一個 主接口、一個遠程接口、一個 bean 類和一個部署描述符。本節介紹以下所有必備 EJB 接口和類的實現。
EJB 主接口 —— RemotePurchaseOrderHome.java(請參見 清單 2)
EJB 遠程接口 —— RemotePurchaseOrder.java( 請參見 清單 3)
EJB bean 類 —— PurchaseOrderEJB.java(請參見 清單 4 和 清單 5)
描述符 —— ejb-jar.xml 和 openejb-jar.xml(請參見 清單 6 和 清單 7)
可在 $part1.home/src/examples/po/ejb 目錄 下找到 PurchaseOrderEJB 的源文件(.java),兩個描述符文件均位於 $part1.home /dds 目錄中。
EJB 主接口
EJB 主接 口用於在服務器中創建、刪除或查找 bean 實例。這個接口在部署期間綁定到服務器 Java Naming and Directory Interface(JNDI)樹 中。在運行時,客戶機程序查找 JNDI 中的主接口來使用此 EJB。RemotePurchaseOrderHome 是 PurchaseOrderEJB 的主接口,它按照 EJB 2.1 規范實現了 EJBHome(參見 清單 2)。
清單 2. PurchaseOrderEJB 的主接口
public interface RemotePurchaseOrderHome
extends EJBHome, Remote {
public RemotePurchaseOrder create(String purchaseOrderNum,
String item,
String description,
Integer unitPrice,
Integer quantity,
String requestorEmail)
throws CreateException, RemoteException;
public Collection findByRequestorEmail(String requestorEmail)
throws FinderException, RemoteException;
public RemotePurchaseOrder findByPrimaryKey(String poNum)
throws FinderException, RemoteException;
}
您有一個 create() 方法,它實際上是一個 bean 實例,在 PURCHASEORDER 表中插入新記錄。存在兩個 finder 方法:
findByRequestorEmail() 返回指定請求者電子郵件為 REQUESTOREMAIL 的采購訂單行的集合。
findByPrimaryKey 返回帶有特定主鍵的一行。
EJB 遠程接口
EJB 遠程接口(參見 清單 3)為實體 bean 定義業務方法。遠程接口中的這些方法應定義為公共方法,以使其可被遠程客戶機程序所 訪問。RemotePurchaseOrder 是 PurchaseOrderEJB 的遠程接口。
清單 3. PurchaseOrderEJB 的遠程接口
public interface RemotePurchaseOrder
extends EJBObject, Remote {
public String getPurchaseOrderNum()
throws RemoteException;
public void setPurchaseOrderNum(String purchaseOrderNum)
throws RemoteException;
public String getItem()
throws RemoteException;
public void setItem(String item)
throws RemoteException;
public String getDescription()
throws RemoteException;
public void setDescription(String description)
throws RemoteException;
public Integer getUnitPrice()
throws RemoteException;
public void setUnitPrice(Integer unitPrice)
throws RemoteException;
public Integer getQuantity()
throws RemoteException;
public void setQuantity(Integer quantity)
throws RemoteException;
public String getRequestorEmail()
throws RemoteException;
public void setRequestorEmail(String requestorEmail)
throws RemoteException;
}
RemotePurchaseOrder 為數據庫表 PURCHASEORDERSYSTEM 中的列定義了 getters 和 setters。
EJB bean 類
EJB bean 類實現遠程接口,並為遠程接口中定義的方法提供實際實現。了解此 EJB 是如何作為 CMP 部署之後,您不必實現 getter 和 setter 方法,而是將其定義為抽象方法(參見 清單 4)。
清單 4. PurchaseOrderEJB bean 類的 getter 和 setter 方法
public abstract class PurchaseOrderEJB
implements EntityBean {
private EntityContext context;
// Access methods for the CMP Fields.
public abstract String getPurchaseOrderNum ();
public abstract void setPurchaseOrderNum (String
purchaseOrderNum);
public abstract String getItem ();
public abstract void setItem (String item);
...
create() 方法(參見 清單 5)在 PURCHASEORDER 表中插入一個帶有特定參數值的新行。
清單 5. 主接口中定義的 bean 類內的 create() 方法實現
private String create (String purchaseOrderNum,
String item,
String description,
Integer unitPrice,
Integer quantity,
String requestorEmail)
throws CreateException {
setPurchaseOrderNum(purchaseOrderNum);
setItem(item);
setDescription(description);
setUnitPrice(unitPrice);
setQuantity(quantity);
setRequestorEmail(requestorEmail);
return purchaseOrderNum;
}
下面定義 EJB 描述符。
定義 EJB 描述符
既然已經創建好了遠程接口、主接口和 bean 類,那麼接下來就該轉向部署描述符了。您將定義兩個描述符:根據 EJB 2.1 規范定義 的 ejb-jar.xml 以及在 Geronimo 中部署 EJB 所必需的 openejb-jar.xml。如 清單 6 所示,ejb-jar.xml 定義主接口、遠程接口和 bean 類。請注意,持久性類型為 Container。
清單 6. ejb-jar.xml 片段
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="2.1"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
<display-name>PurchaseOrderEJB</display-name>
<enterprise-beans>
<entity>
<display-name>PurchaseOrderEJB</display-name>
<ejb-name>;PurchaseOrderEJB</ejb-name>
<home>;examples.po.ejb.RemotePurchaseOrderHome</home>
<remote>;examples.po.ejb.RemotePurchaseOrder</remote>
<ejb-class>;examples.po.ejb.PurchaseOrderEJB</ejb-class>
<persistence-type>Container</persistence-type>;
<prim-key-class>;java.lang.String</prim-key-class>
<reentrant>false</reentrant>
<cmp-version>2.x</cmp-version>
<abstract-schema-name>PurchaseOrder</abstract-schema-name>
<cmp-field>
<field-name>purchaseOrderNum</field-name>
</cmp-field>
<cmp-field>
<field-name>item</field-name>
</cmp-field>
<cmp-field>
<field-name>description</field-name>
</cmp-field>
...
<primkey-field>purchaseOrderNum</primkey-field>
<resource-ref>
<description>Reference to Datasource in Geronimo.
</description>
<res-ref-name>jdbc/PurchaseOrderDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
EJB 描述符 ejb-jar.xml 為一個 EJB 定義了 CMP 字段(參見 清單 7)。這些字段對應於 PurchaseOrderEJB 將與之交互的底層數據 庫表的列。您還在 resorce-ref 部分中定義了綁定在服務器 JNDI 中的數據庫數據源(jdbc/PurchaseOrderDataSource)。
描述符中定義的兩個 finder 方法是:
findByPrimaryKey,給定采購訂單號,返回采購訂單。
findByRequestoEmail,返回一個特定電子郵件地址所請求的采購訂單集合。
ejb-jar.xml 用 EJB Query Language(EJBQL)為上述 finder 方法定義查詢。
清單 7. 在 ejb-jar.xml 中為 findByXXX() 方法定義查詢
<query>
<description/>
<query-method>
<method-name>findByRequestorEmail</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>SELECT OBJECT(p) FROM PurchaseOrder AS p WHERE
p.requestorEmail = ?1
</ejb-ql>
</query>
<query>
<description/>
<query-method>
<method-name>findByPrimaryKey</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>SELECT OBJECT(p) FROM PurchaseOrder AS p WHERE
p.purchaseOrderNum = ?1
</ejb-ql>
</query>
隨 Java EE 描述符 ejb-jar.xml 一起,您還必須為 Geronimo 定義一個 EJB 描述符(openejb-jar.xml),如 清單 8 所示。
清單 8. openejb-jar.xml 片段
<?xml version="1.0" encoding="UTF-8"?>
<openejb-jar
xmlns="http://www.openejb.org/xml/ns/openejb-jar-2.0"
xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.0"
xmlns:security="http://geronimo.apache.org/xml/ns/security-1.1"
xmlns:sys="http://geronimo.apache.org/xml/ns/deployment-1.0"
xmlns:pkgen="http://www.openejb.org/xml/ns/pkgen-2.0"
inverseClassloading="true"
configId="PurchaseOrderEJB" parentId="geronimo/activemq/1.0/car">
<!--This is a reference to Datasource
(maps to Embedded Database Table) configured in Geronimo Server. -->
<cmp-connection-factory>
<resource-link>PurchaseOrderDataSource</resource-link>
</cmp-connection-factory>
<enterprise-beans>
<entity>
<ejb-name>PurchaseOrderEJB</ejb-name>
<jndi-name>PurchaseOrderEJB</jndi-name>
<table-name>PurchaseOrder</table-name>
<cmp-field-mapping>
<cmp-field-name>purchaseOrderNum</cmp-field-name>
<table-column>purchaseOrderNum</table-column>
</cmp-field-mapping>
<cmp-field-mapping>
<cmp-field-name>item</cmp-field-name>
<table-column>item</table-column>
</cmp-field-mapping>
...
<primkey-field>purchaseOrderNum</primkey-field>
清單 8 中的這個描述符定義了 JNDI 名稱(PurchaseOrder),該名稱用於將 EJM 主接口綁定到服務器 JNDI 樹中。<ejb- name> 應與 ejb-jar.xml 中使用的名稱匹配。Java EE 服務器將使用該描述符中定義的表名和 cmp 字段來執行數據庫操作。