對於那些有多個企業信息系統的公司,把這些企業信息系統整合起來是實現最高效率是至關重要的。JCA(Java Connector Architecture) 提供了一個應用服務器和企業信息系統連接的標准Java解決方案,以及把這些系統整合起來實現最好的工作效率的方法。因為J2EE對企業級應用程序集成的支持已經非常小了(本質上,JMS和XML可以使用JAX應用程序編程接口支持它),Sun和它的Java Community Process伙計建議把J2EE Connector Architecture ( J2EE連接器體系結構,JCA)作為J2EE規范的1.3版的一部分。因為JCA提供了整合不同種類的企業信息系統的一套標准的體系結構,使用它的企業信息系統供應商就不再需要為每個應用服務器定制它們的產品。遵守這個規范的應用程序服務器供應商在它們想增加新的企業信息系統連接的時候將不需要添加自定義代碼了。最好的事情就是,需要從J2EE應用程序中訪問企業信息系統產品的用戶不需要學習或者再學習不同的應用程序編程接口,因為JCA定義了一套公共的客戶接口。
JCA內幕
JCA定義了一套標准的接口,用於讓連接器把兼容的應用程序服務器無縫的整合起來。同時,另一套標准接口允許客戶(或者應用程序服務器的應用程序主機)用一種統一的方法使用連接器。這樣通過JCA連接器對於跨應用程序服務器來說就是可移植的,而客戶程序也是很輕便的連接器。
遵守JCA規范的連接器被稱作資源適配器(resource adapter)。每個資源適配器都被要求支持兩套標准接口∶一組接口被應用程序服務器使用來與適配器交互作用,而另一套由客戶/消費者使用與企業信息系統(當然也是通過適配器)相互作用。
JCA考慮到資源適配器可以把客戶端程序作為專有接口的替代,但是這可能會在後續的版本改變。我強烈地建議你始終支持標准客戶端。其實也並沒有多少額外的工作要做,就像我在這個例子資源適配器中要演示的一樣。但是,在我們深入研究這個例子之前,讓我們先來看看系統-客戶端接口。每個遵守JCA規范的資源適配器必須支持一套應用程序服務器用來管理適配器的標准接口。這套接口是在適配器和應用程序服務器之間的系統級協約,是由JCA委托的。
一台應用服務器可以有多個資源適配器,但是每個企業級信息系統類型只有一個適配器。舉例來說,一台應用服務器可以為SAP,Oracle各自分配一個資源適配器。應用服務器和資源適配器關系緊密共同管理企業信息系統訪問中的三個關鍵性方面∶連接,事務和安全。為此目的,JCA定義了一套標准接口,在javax.resource.spi和javax.resource.spi.security包中定義,是所有的資源適配器必須實現的。這些接口允許應用服務器與資源適配器相互作用並且控制連接,事務和安全管理的處理。
創建資源適配器首先要正確地實現系統協定,而且它也帶來了許多好處,我們舉例子來說明其中一個好處,使用一個有連接管理協定的資源適配器能使應用程序服務器連接到一個基本企業級信息系統。這使一個可縮放的應用程序環境可以支持很多的需要訪問企業級信息系統的客戶。
另一個好處可以通過實現事務管理協定來實現,這就是支持對企業級信息系統資源管理者的事務性訪問。這個協定使一個應用程序服務器能夠使用事項管理程序在多個資源管理程序之間管理事務。比如說,這將允許一個事務會話bean通過Java數據庫連接(JDBC)和一個應用程序服務器控制的相同事務中的SAP系統訪問一個關系數據庫。事務協約也支持那些被一個企業級信息系統資源管理程序內部管理的事務,也就是本地事務,而不必涉及企業外部事務管理。
還有一個好處就是充分考慮訪問企業級信息系統的安全性協定。這個協定提供了對安全的應用環境的支持,減少了威脅信息系統安全的可能,並且還能夠保護信息系統管理的有商業價值的信息資源。
這些優點均可用於應用程序服務器的資源適配器,而不必再寫任何自定義代碼。這就大大刺激了企業信息系統的提供商為系統創建高級的資源適配器。
此外,JCA還定義了客戶/消費者使用的另一套與企業信息系統交互的接口。JCA調用了公共客戶接口( Common Client Interface,CCI)。CCI是一個應用程序開發者和客戶程序可以共同連接和訪問後端系統的程序接口。它是一個類似於JDBC的低端的API。CCI管理應用程序和系統之間的數據流動,而不會讓我們看到任何的容器和應用程序服務器所做的事情。CCI是為了某些特別的目的設計的。首先,它能夠跨越許多種類型的企業信息系統;其次,這個應用程序接口被設計的非常易用,而且是可擴展的。CCI可以更進一步的構建更具體的企業信息系統功能。
CCI被分成四部分(見表1)。所有的具體CCI類和接口都可以方便的在javax.resource.cci程序包中被找到。
接口類型 名稱 與連接有關的接口,描述一個工廠類連接和一個應用程序類連接。 javax.resource.cci.ConnectionFactory
上下文環境中的資源適配器
為了能夠把問題解釋得清清楚楚,讓我們看一下圖一。注意,存在於應用程序服務器進程空間中的資源適配器,通過資源適配器實現的系統接口執行連接、事務和安全管理。像EJB,servlet和JSP這樣的客戶應用程序,就可以通過CCI與資源適配器交互。
資源適配器的例程
現在我們開始一個資源適配器的例程。不過先聲明一點,我在這裡的目標不是要提供一個高質量的適配器來與像SAP這樣的ERP系統交互,也就是說只能供大家參考研究而不能用於商業上的目的。我的目的是舉例說明一個最基本的資源適配器。另外,我想把注意力都放在創建資源適配器的連接管理這一點上,我們也可以稱之為資源適配器的" hello world "程序吧,主要因為基本概念還是比較重要的。用於這個資源適配器例程的企業級信息系統是一個屬性文件,所以最後,你將得到一個可以從屬性文件中讀取屬性完整的可運行的資源適配器(而且,更重要地是我們要理解它是如何運行)。
下面,我給出了所有的本文所涉及的代碼。在我們開始研究這段資源適配器源代碼之前,讓我們看一下從一個客戶應用程序(EJB,servlet等等)決定使用資源適配器到應用程序使用它的步驟。首先,應用程序服務器啟動。為了配置(或插入)資源適配器,應用程序服務器創建了一個ManagedConnectionfactory並在其上調用createmanagedconnection()方法。如果應用程序服務器委托創建10個連接(這個數目在資源適配器部署期間已經被指定了),它重復這個步驟10次。ManagedConnection的每個實例都處於應用程序服務器的連接池內。連接池實現與我們討論的問題無關。資源適配器部署配置能控制連接池的參數,例如插入池中的初始連接有多少,池中的最大的連接數,每個連接的生命周期等等。應用程序服務器也使用JavaBean調用約定調用每個ManagedConnectionFactory上的設置方法來設置具體的資源適配器的屬性,這也被作為部署信息的一部分來指定。例如,在我們的資源適配器實例中指定的屬性是ConnectionURL,將指定要連接到的屬性文件。應用程序服務器調用ManagedConnectionFactory上的setConnectionURL ()方法來設置這個屬性。
此時,應用程序已經啟動並且在應用程序服務器中已經有了我們指定數量的ManagedConnections。一個要求訪問企業信息系統的客戶先要從引用JNDI的ConnectionFactory實例開始。應用程序服務器在這裡監聽,然後調用ManagedConnectionFactory實例的createConnectionFactory ()方法
ConnectionFactory使用應用程序服務器ConnectionManager初始化後的實例。
一旦客戶程序取得ConnectionFactory的引用,它就調用getConnection ()方法。getConnection ()方法是可以用來調用ConnectionManager的allocateConnection ()方法的代碼。這個方法最後調用ManagedConnectionFactory上的matchManagedConnections ()方法。如果matchManagedConnections ()方法決定了ManagedConnection中的一個連接可以被使用,它返回到這個應用程序服務器的連接,而在這樣情況下服務器只是簡單地調用返回的ManagedConnection實例上的getConnection ()方法。如果存在的ManagedConnection沒有可以被使用的,那麼應用程序服務器創建一個新的連接直到到了最大值(部署期間指定的)。然後應用程序服務器調用新創建的managedconnection實例的getConnection ()方法。用兩種方法,都會把一個連接實例返回到客戶程序:通過私有接口或者CCI與企業信息系統交互作用。 代碼段1是一個客戶端程序(一個EJB)的片段,使用屬性文件資源適配器 來訪問一個名為Message的屬性。注意CCI要實現的屬性文件適配器,是EJB用來訪問企業信息系統(屬性文件)的。
當一個客戶程序運用CCI與資源適配器交互作用時,連接上的全部的輸入和輸出都是通過一個交互對象實現的(見代碼段2,InteractionImpl.java)
交互對象通過一個Record對象來起作用。Record對象封裝客戶程序請求而且還也封裝企業信息系統響應,封裝出現的方法對於具體的供應廠商來說都不一樣。例如,在我們的例子中,客戶程序只是創建一個MappedRecord 並且添加它在Record對象中所要作為鍵檢索的所有的屬性名(見代碼段2,MappedRecord的一個例子) 。客戶程序然後調用交互作用的execute ()方法,當方法返回時,Mapped Record中的每個屬性值卻將被放入Record對象中,作為對應關鍵字的值,參看代碼段2中的exec ()方法。
當客戶程序結束使用連接時,它就會調用close ()方法。 實現close ()方法是為了通知應用程序服務器ManageConnection創建的Connection可以被放回可用連接池。應用程序服務器調用ManagedConnection的清除方法,然後把它送回連接池中,除此之外,如果創建一個新的滿足接續申請的ManagedConnection的話,應用程序服務器還可以調用destroy ()方法。最終,當應用程序服務器關閉或者卸載資源適配器時,它會調用連接池中的每個ManagedConnection的destroy ()方法。
圖2是一幅UML序列圖表描述時間的序列
建議按下面的順序研究源文件∶
要想遵循系統協定,就按照ManageConnectionFactoryImpl.java, ManagedConnectionImpl.java, ConnectionImpl.java, 和ConnectionEventListenerImpl.java的順序讀。
要想遵循客戶程序協定,就請按照ConnectionFactoryImpl.java, ConnectionImpl.java, RecordFactoryImpl.java, InteractionImpl.java, and MappedRecordImpl.java.
剩下的其他的類。
測試資源適配器
我是使用BEA的應用程序服務器WebLogic 6.1來測試資源適配器,本教程就想用這個應用軟件服務器來做演示。
進入屬性文件目錄,然後從命令行方式運行build.cmd,該文件假定你的應用程序服務器被安裝在C:\bea\wlserver6.1_beta目錄。如果不是的話,請在運行之前在build.cmd文件中做出相應的改動。運行這個文件將生成一個資源適配器存檔文件( Resource Adapter Archive,RAR),配置WebLogic應用程序服務器。想知道RAR文件的詳細信息,請參看JCA規范。如果你的應用程序服務器還沒有運行的話,就請先啟動它。你將看到應用程序服務器創建10個管理連接。在實際工作中,這個數目將取決於你在具體的企業信息系統中能得到的同時連接許可證的數目。
要想看看在運行中的資源適配器,請在test目錄下打開一個命令行,然後運行build.cmd。這將配置一個使用實例資源適配器的無狀態會話bean並且編譯一個測試程序(Client.java),這個程序調用bean的sayHello ()方法。在命令行裡輸入Client.cmd之前,請先確定在C:\temp目錄下有一個名為test.properties的屬性文件。這個屬性文件應該至少有一個屬性Message (區分大小寫)設置為某個字符串,例如Message = Hello World。確定你遵守應用程序服務器窗口中的信息,並且遵循從我們的資源適配器輸出的信息。這些信息將幫助你了解在應用程序服務器,資源適配器和無狀態會話bean之間的交互作用。這些信息還將有助於證實描述在圖2中的步驟流程。
然而,JCA還有很多缺點,象缺乏對於異步企業信息系統訪問的支持以及對於基於XML訪問的直接支持。希望在後面的版本中能夠解決這些問題。不用懷疑,JCA填充了J2EE技術的一個巨大的缺陷,可以給服務器應用程序的開發帶來很好的前景。
代碼段一:
initCtx = new InitialContext();
javax.resource.cci.ConnectionFactory cf =
(javax.resource.cci.ConnectionFactory)
initCtx.lookup("java:comp/env/eis/
PropertiesFileAdapter");
System.out.println("Got ConnectionFactory. Now
calling getConnection()");
javax.resource.cci.Connection myCon =
cf.getConnection();
javax.resource.cci.Interaction interaction =
myCon.createInteraction();
javax.resource.cci.MappedRecord recordIn =
cf.getRecordFactory().createMappedRecord("");
recordIn.put("Message","");
javax.resource.cci.MappedRecord recordOut =
(javax.resource.cci.MappedRecord)
interaction.execute(null,
(javax.resource.cci.Record)recordIn);
myCon.close();
retVal = (String)recordOut.get("Message");
代碼段二:
package adapters.propertiesfile;
import java.util.*;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.IllegalStateException;
import javax.resource.cci.*;
import java.lang.reflect.*;
import java.lang.*;
public class InteractionImpl implements
Interaction {
Connection con = null;
public InteractionImpl(Connection con) {
System.out.println("InteractionImpl::
Constructor called wth a connection of
class " + con.getClass().toString());
this.con = con;
}
public javax.resource.cci.Connection
getConnection() {
return con;
}
public void close() throws ResourceException {
con = null;
}
public boolean execute (InteractionSpec ispec,
Record input, Record output)
throws ResourceException {
if(!(input instanceof MappedRecord) ||
!(output instanceof MappedRecord))
throw new ResourceException("Both input and
output records must be Mapped Records.");
output = exec((MappedRecord)input,(
MappedRecord)output);
if (output != null) {
return true;
} else {
return false;
}
}
public Record execute (InteractionSpec ispec,
Record input)
throws ResourceException {
if(!(input instanceof MappedRecord))
throw new ResourceException(
"Input record must be a Mapped Record.");
MappedRecord output = new MappedRecordImpl();
return exec((MappedRecord)input,output);
}
public ResourceWarning getWarnings()
throws ResourceException {
return null;
}
public void clearWarnings()
throws ResourceException {
}
Record exec(MappedRecord input, MappedRecord
output) throws ResourceException {
try
{
System.out.println(
"InteractionImpl::exec called");
Properties props =
((ConnectionImpl)con).getProperties();
Set keys = input.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
String key = (String)it.next();
output.put(key,props.get(key));
}
return output;
}
catch(Exception e)
{
throw new ResourceException(e.getMessage());
}
}
}
代碼段三:
package adapters.propertiesfile;
import java.util.*;
public class MappedRecordImpl implements
javax.resource.cci.MappedRecord {
private String recordName;
private String description;
private HashMap mappedRecord;
public MappedRecordImpl() {
mappedRecord= new HashMap();
}
public MappedRecordImpl (String name) {
mappedRecord = new HashMap();
recordName = name;
}
public String getRecordName() {
return recordName;
}
public void setRecordName(String name) {
recordName = name;
}
public String getRecordShortDescription() {
return description;
}
public void setRecordShortDescription(
String description) {
description = description;
}
public boolean equals(Object other) {
if(!(other instanceof MappedRecordImpl))
return false;
MappedRecordImpl m = (MappedRecordImpl)other;
return (recordName == m.recordName) &&
mappedRecord.equals(m.mappedRecord);
}
public int hashCode() {
String result = "" + recordName;
return result.hashCode();
}
public Object clone() throws
CloneNotSupportedException {
return this.clone();
}
public void clear()
{
mappedRecord.clear();
}
public boolean containsKey(Object key)
{
return mappedRecord.containsKey(key);
}
public boolean containsValue(Object value)
{
return mappedRecord.containsValue(value);
}
public Set entrySet()
{
return mappedRecord.entrySet();
}
public Object get(Object o)
{
return mappedRecord.get(o);
}
public boolean isEmpty()
{
return mappedRecord.isEmpty();
}
public Set keySet()
{
return mappedRecord.keySet();
}
public Object put(Object key, Object value)
{
return mappedRecord.put(key,value);
}
public void putAll(Map c)
{
mappedRecord.putAll(c);
}
public Object remove(Object o)
{
return mappedRecord.remove(o);
}
public int size()
{
return mappedRecord.size();
}
public Collection values()
{
return mappedRecord.values();
}
}