通过JCA实现企业级应用程序的“即插即用”

  对于那些有多个企业信息系统的公司,把这些企业信息系统整合起来是实现最高效率是至关重要的。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
javax.resource.cci.Connection
javax.resource.cci.ConnectionSpec
javax.resource.cci.LocalTransaction
与交互有关的接口,能使组件驱动一个与EIS实例的交互。 javax.resource.cci.Interaction
javax.resource.cci.InteractionSpec
与数据表现有关的接口,用来描述与EIS实例交互中涉及到的数据结构。 javax.resource.cci.RecordFactory
javax.resource.cci.Record, javax.resource.cci.MappedRecord, javax.resource.cci.IndexedRecord
javax.resource.cci.IndexedRecord
javax.resource.cci.ResultSet
java.sql.ResultSetMetaData
与元数据有关的接口,提供了一个资源适配器与EIS连接的基本的元信息。 javax.resource.cci.ConnectionMetaData
javax.resource.cci.ResourceAdapterMetaData
  上下文环境中的资源适配器

  为了能够把问题解释得清清楚楚,让我们看一下图一。注意,存在于应用程序服务器进程空间中的资源适配器,通过资源适配器实现的系统接口执行连接、事务和安全管理。像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();
}
}

摘自:http://www.yesky.com/SoftChannel/72342371961929728/20020607/1614922.shtml
 
posted @ 2006-10-30 12:09  Rookie.Zhang  阅读(409)  评论(0编辑  收藏  举报