2003 年 8 月 21 日
本文基於J2EE連接器體系結構,介紹一個典型的資源適配器案例開發的過程和開發技巧,然後開發客戶端,並在客戶端通過連接器調用資源層。學習完本文,讀者將能理解JCA的體系結構和開發的各個細節,並且能自主開發新的J2EE連接器。閱讀本文,您需要以下的知識和工具:
- 至少一種J2EE應用服務器的使用經驗;
- 能夠開發、部署EJB,並且能夠在客戶端調用;
- Java Socket編程、線程的基礎知識。
本文的參考資料見 參考資料
J2EE連接器(JCA)是一種企業級應用整合的技術。目前,在J2EE平臺中,常使用的應用整合的技術有:
- Java消息服務(JMS);
- Web服務(Web Services);
- J2EE 連接器體系結構(JCA)。
Java消息服務是一組在Java程序中使用企業級消息的API,它爲整合J2EE應用和非J2EE應用程序提供了異步整合的方式,在這種方式裏,所有的應用都和消息中間件(MOM)進行通信,這樣就提供了與平臺無關、語言無關的整合。Web服務是一種新興發展起來的技術,它使用SOAP消息作爲傳輸的載體,使用HTTP或者其它基於文本的協議作爲數據傳輸的協議,Web服務可以是同步的整合,也可以進行異步的整合。同樣,Web服務也是一種和平臺無關、和開發語言無關的整合技術。
J2EE連接器技術爲連接J2EE應用服務器和已有的企業應用系統(ERP/CRM等)提供瞭解決方案。通過J2EE連接器,EIS(企業信息系統)廠商不需要再爲每個應用服務器提供專門的支持接口,同樣,應用服務器廠商在連接到新的EIS系統時也不需要再重新開發新的代碼,JCA爲整合企業資源提供了標準的解決方案。
在JCA1.0規範中,它定義了應用服務器和資源適配器的系統級合同(連接池、事務管理和安全),爲資源適配器的客戶端定義了通用的客戶端接口(Common Client Interface,CCI),同樣也規範了JCA打包和部署等細節。但在JCA1.0規範中,只支持OutBound的調用,也就是說只能在J2EE應用中通過資源適配器向外調用企業資源層,而企業資源層不能通過適配器調用J2EE裏的資源。在即將發佈的JCA1.5規範中,這個問題得到了解決,也就是說,在J2EE的外部可以通過資源適配器直接調用部署在J2EE中的應用,如EJB。
下面簡單看一下JCA的體系結構,如圖1所示。
下面解釋一下上圖中的一些概念。
資源適配器(Resource Adapter):爲了獲得在應用服務器和EIS之間的系統標準可插入性,JCA定義了應用服務器和EIS之間的一系列合約(Contract),資源適配器實現了EIS端的系統級合約。
系統級合同(System Contract):系統級合同定義了一組系統合同,可以讓應用服務器和資源適配器連接起來以管理連接、事務和安全性。這樣,應用組件的開發者就可以把精力集中與和業務邏輯相關的開發,而沒有必要關心系統級的問題。
客戶通用接口(CCI):定義了J2EE組件連接到EIS系統的一組通用的API,這些API在具體的開發中進行實現。
在連接器的開發中,主要任務就是開發資源適配器。如果需要,再開發出一套客戶通用接口實現(CCI),這樣,客戶端就可以通過這些通用的接口來連接、使用EIS層的資源了。
在使用連接池的情況下,應用程序組件和JCA以及EIS交互關係如圖2所示。
我們簡要看一下請求傳遞的順序:
- 應用程序組件發出獲得連接的請求;
- 連接工廠調用連接管理器的allocateConnection;
- 連接管理器向連接池管理器發出獲得連接的請求;
- 連接池管理器試圖從ManagedConnectionFactory進行連接匹配,如果沒有匹配到連接,那麼返回null;
- 由於沒有匹配到連接,連接池管理器調用ManagedConnectionFactory的createManagedConnection方法來創建連接;
- ManagedConnectionFactory接收到連接池管理器的請求後,創建一個ManagedConnection實例,同時ManagedConnection打開和EIS之間的物理連接,然後把這個ManagedConnection實例返回給連接池管理器;
- 連接池管理器調用ManagedConnection實例的getConnection方法以獲得一個Connection實例;
- ManagedConnection實例收到連接池管理器的getConnection請求後,創建一個Connection實例,然後把這個實例返回給連接池管理器;
- 這個Connection實例通過連接池管理順次返回給應用程序組件;
- 應用程序組件通過返回的Connection來創建Interaction或者調用業務方法;
- 應用程序組件通過Connection調用業務方法時,實際上Connection使用了ManagedConnection的物理連接和EIS進行交互。
下面我們介紹一個簡單的案例的開發。
|
這個案例使用了典型的J2EE多層體系結構,如圖3所示。EIS層是一個簡單的能處理多線程的Socket服務程序,當它接收到客戶端發送來的字符串時,就在這個字符串前增加"Hello:"字符串,然後返回。資源適配器用於連接到EIS,使得J2EE應用(如EJB)能夠通過它調用EIS層。這個案例的客戶端有兩種,一種是基於瀏覽器的客戶端,它通過HTTP來訪問Web服務器上的JSP組件,JSP組件通過RMI調用EJB來訪問EIS;另一種客戶端是普通的Java程序,它通過RMI來調用部署在EJB服務器中的EJB組件以訪問EIS。
下面我們看簡單資源層的代碼。
|
資源層是一個Socket服務程序,當它接收到客戶端發送來的字符串時,就在這個字符串前增加"Hello:"字符串,然後返回這個新的字符串。代碼如例程1所示。
package com.hellking.jca.eis; import java.net.*; import java.io.*; public class EISServer { public static void main(String[] args) { try { System.out.println ("啓動服務...."); ServerSocket s = new ServerSocket (2008); // 處理客戶端請求 while (true) { System.out.println ("監聽客戶端連接..."); new ServerThread(s.accept()).start(); System.out.println ("接收到一個連接"); } } catch(Exception e) { e.printStackTrace(System.err); } } } class ServerThread extends Thread { private Socket socket=null; public ServerThread(Socket socket) { super("a new thread"); this.socket=socket; } public void run() { try { BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream())); PrintStream out = new PrintStream(socket.getOutputStream()); String line; do { line = in.readLine(); System.out.println ("接收到以下輸入: " + line); if (line != null) { out.println ("Hello: "+line); } } while (line != null); System.out.println ("關閉連接"); socket.close(); } catch(Exception e) { e.printStackTrace(); } } } |
|
開發資源適配器,我們從最基本的連接(Connection)類開始。
DemoConnection DemoConnection擴展了CCI的Connection接口,它由客戶端程序使用,代表了到EIS的"虛擬"連接,通過這個"虛擬"的連接客戶端可以調用EIS。需要注意的是,虛擬連接關閉時,物理連接不一定關閉。DemoConnection定義了資源適配器所實現的業務方法。如例程2所示。
package com.hellking.jca; import javax.resource.cci.Connection; import javax.resource.ResourceException; public interface DemoConnection extends Connection { //業務方法 public String sayHello(String name)throws ResourceException; } |
DemoConnectionImpl DemoConnectionImpl是DemoConnection的實現類,它通過ManagedConnection來完成具體的業務方法。ManagedConnection是代表到EIS的物理連接,將在後面介紹。
package com.hellking.jca; import javax.resource.*; import javax.resource.spi.*; import javax.resource.cci.*; import javax.security.auth.*; import java.util.*; import java.io.*; //連接實現類,它通過DemoManagedConnection來完成具體的任務 public class DemoConnectionImpl implements DemoConnection { protected PrintWriter out;//logOut protected DemoManagedConnection demoManagedConnection; //關閉連接,釋放資源 public void close() { if (demoManagedConnection == null) return; demoManagedConnection.removeConnection(this); demoManagedConnection.connectionClosedEvent(); demoManagedConnection = null; } //返回和這個連接關聯的被管理連接 public DemoManagedConnection getManager() { return demoManagedConnection; } //設置和這個連接關聯的被管理連接 public void setManager (DemoManagedConnection manager) { this.demoManagedConnection =manager; } //業務方法,它通過調用被管理的連接來實現。 public String sayHello(String name)throws ResourceException { return demoManagedConnection.sayHello (name); } //使連接無效 public void invalidate() { demoManagedConnection = null; } public void setLogWriter(PrintWriter out) { this.out = out; } public PrintWriter getLogWriter() { return out; } public ConnectionMetaData getMetaData() { return null; } public ResultSetInfo getResultSetInfo() { return null; } public javax.resource.cci.LocalTransaction getLocalTransaction() { return null; } public Interaction createInteraction() { return null; } } |
DemoConnectionFactoryImpl DemoConnectionFactoryImpl也是和CCI相關的類,它實現了ConnectionFactory接口,它主要用於創建客戶端要使用的虛擬連接(DemoConnection)。由於DemoConnectionFactoryImpl類需要在JNDI名字空間中註冊,故它需實現Serializable和Referenceable接口。客戶端查找DemoConnectionFactoryImpl類,然後使用這個類來獲得到EIS的連接。DemoConnectionFactoryImpl代碼如例程4所示。
例程4 DemoConnectionFactoryImpl的代碼
package com.hellking.jca; import javax.resource.*; import javax.resource.spi.*; import javax.resource.cci.*; import javax.naming.*; import java.io.*; public class DemoConnectionFactoryImpl implements ConnectionFactory { protected Reference reference; protected ManagedConnectionFactory manager; protected ConnectionManager connectionManager; protected PrintWriter out;//logOut //構造方法 public DemoConnectionFactoryImpl (ManagedConnectionFactory manager, ConnectionManager connectionManager) { this.manager = manager; //如果連接管理器爲空,那麼創建一個新的連接管理器 if (connectionManager == null) { connectionManager = new DemoConnectionManager(); ((DemoConnectionManager)connectionManager).setLogWriter (out); } else { this.connectionManager = connectionManager; } } //獲得一個連接 public Connection getConnection() throws ResourceException { return (DemoConnection) connectionManager.allocateConnection (manager, null); } //不支持此方法 public Connection getConnection(ConnectionSpec p) throws ResourceException { return null; } public void setReference(Reference ref) { reference = ref; } public Reference getReference() { return reference; } public void setLogWriter(PrintWriter _out) { out = _out; } public PrintWriter getLogWriter() { return out; } public RecordFactory getRecordFactory() throws ResourceException { return null; } public ResourceAdapterMetaData getMetaData() { return null; } } |
DemoConnectionManager DemoConnectionManager是連接的管理器,它爲資源適配器把連接請求傳遞給應用服務器提供了一個切入點。應用服務器實現DemoConnectionManager接口,這個實現不針對具體的資源適配器和連接工廠接口。DemoConnectionManager的任務就是分配連接。對於一些高級的應用,DemoConnectionManager通過和連接池交互來分配連接,在我們開發的這個案例中,DemoConnectionManager直接使用ManagedConnectionFactory來分配連接。ManagedConnectionFactory的代碼如例程5所示。
例程5 ManagedConnectionFactory的代碼
package com.hellking.jca; import java.io.Serializable; import java.io.PrintWriter; import javax.resource.ResourceException; import javax.resource.spi.*; public class DemoConnectionManager implements ConnectionManager, Serializable { protected PrintWriter out; //分配一個連接 public Object allocateConnection (ManagedConnectionFactory managedConnectionFactory, ConnectionRequestInfo connectionRequestInfo) throws ResourceException { ManagedConnection managedConnection = managedConnectionFactory.createManagedConnection(null, connectionRequestInfo); return managedConnection.getConnection(null, connectionRequestInfo); } public void setLogWriter(java.io.PrintWriter out) { this.out =out; } } |
DemoManagedConnectionMetaData DemoManagedConnectionMetaData提供了和ManagedConnection關聯的後臺EIS實例的信息。應用服務器通過這個接口來獲得與它連接的EIS實例的運行環境信息。DemoManagedConnectionMetaData的代碼如例程6所示。
例程6 DemoManagedConnectionMetaData的代碼
package com.hellking.jca; import javax.resource.spi.*; public class DemoManagedConnectionMetaData implements ManagedConnectionMetaData { protected DemoManagedConnection demoManagedConnection; public DemoManagedConnectionMetaData (DemoManagedConnection demoManagedConnection) { this.demoManagedConnection = demoManagedConnection; } //獲得EIS的提供商 public String getEISProductName() { return "Hellking's Simple EIS server"; } //獲得EIS產品的版本 public String getEISProductVersion() { return "Version 1.2"; } //eis支持的最大連接數 public int getMaxConnections() { return 10000; } public String getUserName() { return "Hellking"; } } |
DemoManagedConnection DemoManagedConnection是資源適配器的關鍵所在,它代表了和EIS的物理連接,前面介紹的DemoConnection是由客戶端使用的虛擬連接,虛擬連接要通過物理連接才能使用EIS。一個物理連接可以被多個虛擬連接使用,可以通過associateConnection方法來把虛擬連接和物理連接進行關聯。DemoManagedConnection也提供了產生虛擬連接實例的方法。DemoManagedConnection的代碼如例程7所示。
package com.hellking.jca; import javax.resource.*; import javax.resource.spi.*; import javax.security.auth.*; import java.util.*; import java.io.*; import java.net.*; import javax.transaction.xa.*; //DemoManagedConnection代表了到EIS的物理的連接 public class DemoManagedConnection implements ManagedConnection { protected Socket socket;//和server連接的Socket protected PrintStream serverStream;// protected BufferedReader serverBufferedReader; protected boolean destroyed;//是否銷燬 protected PrintWriter out; protected Set connections = new HashSet();//被管理的連接 protected Set connectionListeners = new HashSet();//連接監聽器 DemoManagedConnectionFactory factory;//連接工廠 public DemoManagedConnection(DemoManagedConnectionFactory factory) { this.factory = factory; } //爲連接增加事件監聽器 public void addConnectionEventListener(ConnectionEventListener l) { connectionListeners.add(l); } //清除連接監聽器 public void removeConnectionEventListener(ConnectionEventListener l) { connectionListeners.remove(l); } //返回連接工廠 public DemoManagedConnectionFactory getFactory () { return factory; } //增加一個連接,並且返回它 public Object getConnection (Subject subject, ConnectionRequestInfo cxRequestInfo) { DemoConnectionImpl connection = new DemoConnectionImpl(); connection.setManager(this); connection.setLogWriter (out); addConnection(connection); return connection; } //清除佔用的資源 public void cleanup() throws ResourceException { destroyedError(); Iterator it = connections.iterator(); while (it.hasNext()) { DemoConnectionImpl demoConnectionImpl = (DemoConnectionImpl) it.next(); demoConnectionImpl.invalidate(); } connections.clear(); } //銷燬所有的物理連接 public void destroy() { if (destroyed) return; Iterator it = connections.iterator(); while (it.hasNext()) { DemoConnectionImpl DemoConnectionImpl = (DemoConnectionImpl) it.next(); DemoConnectionImpl.invalidate(); } connections.clear(); if (socket != null) try {socket.close();} catch (Exception e){} destroyed = true; } //把一個Connection和這個被管理的連接關聯 public void associateConnection(Object _connection) throws ResourceException { destroyedError(); if (_connection instanceof DemoConnection) { DemoConnectionImpl connection = (DemoConnectionImpl)_connection; DemoManagedConnection demoManagedConnection = connection.getManager(); if (demoManagedConnection == this) return; try { demoManagedConnection.removeConnection(connection); } catch(Exception e) { } addConnection(connection); connection.setManager (this); } else { throw new javax.resource.spi.IllegalStateException ("Invalid connection object: " + _connection); } } //不支持此方法 public XAResource getXAResource() throws ResourceException { throw new NotSupportedException("不支持分佈式事務"); } //不支持此方法 public LocalTransaction getLocalTransaction() throws ResourceException { throw new NotSupportedException ("Local transaction not supported"); } //返回元數據 public ManagedConnectionMetaData getMetaData() { return new DemoManagedConnectionMetaData(this); } protected void destroyedError() throws javax.resource.spi.IllegalStateException { if (destroyed) throw new javax.resource.spi.IllegalStateException ("DemoManagedConnection 已經銷燬"); } //增加一個和此被管理連接(ManagedConnection)關聯連接(Connection) protected void addConnection(DemoConnection connection) { connections.add(connection); } //刪除一個和此被管理連接(ManagedConnection)關聯連接(Connection) protected void removeConnection(DemoConnection connection) { connections.remove(connection); } //設置LogWriter public void setLogWriter(PrintWriter _out) { out = _out; } public PrintWriter getLogWriter() { return out; } //判斷是否已經銷燬 public boolean isDestroyed() { return destroyed; } //關閉連接的事件,釋放資源,由連接監聽器來處理 void connectionClosedEvent() { Iterator it = connectionListeners.iterator(); while (it.hasNext()) { ConnectionEventListener listener = (ConnectionEventListener) it.next(); listener.connectionClosed (new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED)); } } //打開物理連接,物理連接是和EIS的正在的連接。通過Socket來進行通信 public void openPhysicalConnection (String serverName, int portNumber) throws UnknownHostException, IOException { socket = new Socket (serverName, portNumber); serverStream = new PrintStream (socket.getOutputStream()); serverBufferedReader = new BufferedReader (new InputStreamReader (socket.getInputStream())); } //業務方法,它是同步的,同時只能一個和Server進行通信 public synchronized String sayHello(String name) throws ResourceException { serverStream.println (name); try { String in = serverBufferedReader.readLine(); return in; } catch (Exception e) { throw new ResourceException ("調用sayHello()發生錯誤: " + e.toString()); } } } |
DemoManagedConnectionFactory DemoManagedConnectionFactory是DemoManagedConnection的工廠,它的主要任務是創建和匹配DemoManagedConnection實例,在創建DemoManagedConnection實例時,DemoManagedConnection實例同時打開到EIS的物理連接。DemoManagedConnectionFactory的代碼如例程8所示。
例程8 DemoManagedConnectionFactory的代碼
package com.hellking.jca; import javax.resource.*; import javax.resource.spi.*; import javax.security.auth.*; import java.util.Set; import java.util.Iterator; import java.io.*; public class DemoManagedConnectionFactory implements ManagedConnectionFactory, Serializable { protected PrintWriter out = new PrintWriter(System.out); private int port;//連接EIS的端口 private String server;//eis服務器的url //創建連接工廠,指定連接工廠的連接管理者爲connectionManager public Object createConnectionFactory (ConnectionManager connectionManager) { DemoConnectionFactoryImpl demoConnectionFactoryImpl = new DemoConnectionFactoryImpl (this, connectionManager); demoConnectionFactoryImpl.setLogWriter(out); return demoConnectionFactoryImpl; } //創建連接工廠,沒有指定連接管理者 public Object createConnectionFactory() { DemoConnectionFactoryImpl demoConnectionFactoryImpl = new DemoConnectionFactoryImpl (this, null); demoConnectionFactoryImpl.setLogWriter(out); return demoConnectionFactoryImpl; } //創建被管理的連接,被管理的連接是和EIS的真實連接,它是物理連接。 public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cri) throws ResourceException { DemoManagedConnection demoManagedConnection = new DemoManagedConnection(this); demoManagedConnection.setLogWriter(out); try { //打開物理連接 System.out.println("打開物理連接....."); demoManagedConnection.openPhysicalConnection (server, port); return demoManagedConnection; } catch (IOException e) { throw new ResourceException (e.toString()); } } //匹配被管理的連接,如果匹配到連接,則返回,否則返回null public ManagedConnection matchManagedConnections (Set connections, Subject subject, ConnectionRequestInfo cri) { Iterator it = connections.iterator(); while (it.hasNext()) { Object obj = it.next(); if (obj instanceof DemoManagedConnection) { DemoManagedConnection demoManagedConnection = (DemoManagedConnection) obj; DemoManagedConnectionFactory demoManagedConnectionf = demoManagedConnection.getFactory(); if (demoManagedConnectionf.equals(this)) { return demoManagedConnection; } } } return null; } public int hashCode() { if (server == null) return 0; return server.hashCode(); } //判斷兩個被管理的連接工廠是否相等 public boolean equals(Object o) { if (o == null) return false; if (!(o instanceof DemoManagedConnectionFactory)) return false; DemoManagedConnectionFactory other = (DemoManagedConnectionFactory)o; if (server.equalsIgnoreCase(other.server) && port == other.port) return true; return false; } public void setLogWriter(java.io.PrintWriter out) { this.out = out; } public PrintWriter getLogWriter() { return out; } public void setServer (String server) { this.server = server; } public String getServer () { return server; } public void setPort (Integer port) { this.port = port.intValue(); } public Integer getPort () { return new Integer(port); } } |
在DemoManagedConnectionFactory類中,設置了兩個屬性,Port和Server,Port表示連接EIS使用的端口,Server表示連接EIS時使用的URL。這兩個屬性需要在資源適配器的部署描述符裏指定具體的值。
|
下面的任務就是在部署描述符裏指定資源適配器的相關接口和實現類。具體需要指定以下的接口和類:
- ManagedConnectionFactory,這裏是DemoManagedConnectionFactory;
- Connectionfactory的接口,這裏是javax..resource.cci.ConnectionFactory;
- Connectionfactory的實現類,這裏是DemoConnectionFactoryImpl;
- 連接的接口,這裏是DemoConnection;
- 連接的實現類,這裏是DemoConnectionImpl。
另外,還需要指定以下的屬性:
- 是否支持事務,這裏是NoTransaction;
- 安全認證的支持,這裏是false;
- DemoManagedConnectionFactory中使用的屬性,其中Server的值爲localhost,Port的值爲2008。
例程9是資源適配器的具體內容,它要保存爲ra.xml放在資源適配器打包文件RAR的META-INF目錄下。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE connector PUBLIC '-//Sun Microsystems, Inc.//DTD Connector 1.0//EN' 'http://java.sun.com/dtd/connector_1_0.dtd'> <connector> <display-name>DemoRA</display-name> <vendor-name>HELLKING</vendor-name> <spec-version>1.0</spec-version> <eis-type>NO TRANS</eis-type> <version>1.2</version> <resourceadapter> <managedconnectionfactory-class>com.hellking.jca.DemoManagedConnectionFactory </managedconnectionfactory-class> <connectionfactory-interface>javax.resource.cci.ConnectionFactory</connectionfactory-interface> <connectionfactory-impl-class>com.hellking.jca.DemoConnectionFactoryImpl </connectionfactory-impl-class> <connection-interface>com.hellking.jca.DemoConnection</connection-interface> <connection-impl-class>com.hellking.jca.DemoConnectionImpl</connection-impl-class> <transaction-support>NoTransaction</transaction-support> <config-property> <config-property-name>Server</config-property-name> <config-property-type>java.lang.String</config-property-type> <config-property-value>localhost</config-property-value> </config-property> <config-property> <config-property-name>Port</config-property-name> <config-property-type>java.lang.Integer</config-property-type> <config-property-value>2008</config-property-value> </config-property> <reauthentication-support>false</reauthentication-support> </resourceadapter> </connector> |
|
通過上面的介紹,相信讀者都JCA的體系結構和開發已經有了全面的瞭解。
下一節介紹資源適配器客戶端的開發,並且在不同的應用服務器平臺下部署這個J2EE應用和資源適配器。