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应用和资源适配器。