本系列共三部分,在第一部分中,Java 开发人员 David Currie 介绍了 Java 2 Enterprise Edition (J2EE) Connector Architecture (JCA) 1.5 中所作的一些优化,它们可以加快现有或新外部资源适配器的运行速度。他还分析了一些使资源适配器焕发生机的新功能。若想提高现有资源适配器的性能或要加入新的功能,或者正在考虑编写新的 JCA 1.5 资源适配器,那么本系列是必不可少的。若要编写使用资源适配器的应用程序,并想知道更多的幕后情况,那么本系列也值得一读。

JCA 1.5 是 J2EE Connector Architecture 的最新版本,它包含了许多重要的增强和新增功能。讨论这些变化的系列文章分为三个部分,本文是第一部分,将介绍该版本中所作的一些优化,它们可以加快那些使用出站资源适配器的应用程序,特别是那些使用事务的应用程序的运行速度。然后介绍对生命周期管理契约所作的扩展,它们可以防止应用程序的连接断开,并使资源适配器焕发新生。这将为第 2、3 部分奠定基础。第 2 部分讨论新的工作管理和事务流入契约,第 3 部分探讨期待已久的消息流入契约,其更常见的叫法是消息驱动的 bean (MDB) 支持。

本文假定读者对 JCA 连接管理契约有一定的了解。原来 1.0 规范中的所有内容仍然适用,因此如果刚刚接触 JCA,建议先看一看 Willy Farrell 的入门教程(请参阅 参考资料)。

连接加快
JCA 规范的新版本不会使应用程序与后端系统之间的连接变得更快,但 JCA 1.5 引入了两组接口,可以加快使用连接的应用程序的运行。第一组接口解除了以前 JCA 版本中应用服务器管理连接句柄方式的限制。

许多读者都知道,J2EE 支持两种连接使用模式,本文称之为 get-use-close cached-handle。对这些模式的进一步分析有助于理解 JCA 1.5 在这方面所带来的性能改善。

get-use-close
在 get-use-close 模式中,应用程序在需要新连接时,总是先获取新连接,然后使用,然后再关闭它,如清单 1 所示。(为清楚起见,本文没有在示例清单中加入异常处理逻辑。)

清单 1. 使用 get-use-close 模式的一个 Enterprise JavaBean (EJB)
public class GetUseCloseEJB implements SessionBean {

    ...

    public void businessMethod() {

       InitialContext context = new InitialContext();
       DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mydatasource");
       Connection connection = datasource.getConnection();

       ...

       connection.close();

    }

}

get-use-close 模式看起来效率不高,但是应用服务器实现的连接池可降低“获得”操作的成本。此外,因为应用程序只在需要时才保留连接,所以应用程序的不同实例或者部分可以重复使用该连接,从而降低了总的资源占用,如图 1 所示。

图 1. 使用中的 get-use-close 模式
使用中的 get-use-close 模式

如图 1 所示,每当 bean 方法调用 getConnection 时,连接管理器都会重复使用池中托管的连接,并只创建一个新连接句柄。当连接句柄通知连接管理器它已关闭时,托管的连接就被清除并返回池中。图 1 中的绿色线条表示托管连接与应用程序的这个实例相关联的时间。

cached-handle
在 cached-handle 模式中,应用程序在一开始是获得连接,然后在一个实例字段中缓存对它的引用,如清单 2 所示。

清单 2. cached-handle 模式
public class CachedHandleEJB implements SessionBean {

    private Connection _connection;

    ...

    public void ejbCreate() {

       InitialContext context = new InitialContext();
       DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mydatasource");
       _connection = datasource.getConnection();

    }

    public void businessMethod() {

       ...

    }

}

应用程序开发人员通常都使用 cached-handle 方法,因为他们认为这样应用程序的性能会更好。但是因为 get-use-close 模式中连接池的作用,这两种使用模式的性能差别一般来说是很小的。尽管 cached-handle 模式可使业务方法中的逻辑更简单,但是需要额外的逻辑以便在钝化(passivation)时关闭连接,并在激活时重建连接。当容器毁坏 bean 时(如由于方法生成了一个运行时异常),还有可能留下打开的连接。

cached-handle 模式的最大问题是当 bean 或者 servlet 的一个实例使用该连接时,其他实例就不能使用它——因此连接数最多只会有实例那么多。如图 2 所示。

图 2. 使用中的 cached-handle 模式
使用中的 cached-handle 模式

从图 2 中可以看到,创建 EJB 时(从绿色线条开始),托管的连接与连接句柄关联,并且这个 bean 实例以外的任何其他对象都不能使用这个连接。(绿色线条无限延伸。)

针对这种情况,JCA 1.5 规范引入了两个新的接口,如清单 3 所示。

清单 3. 解除关联(dissociation)和惰性关联(lazy association)接口
public interface DissociatableManagedConnection {

    void dissociateConnections() throws ResourceException;

}

public interface LazyAssociatableConnectionManager {

    void associateConnection(Object connection,
                             ManagedConnectionFactory mcf,
                             ConnectionRequestInfo cxReqInfo)
            throws ResourceException

}

解除关联
资源适配器的托管连接实现第一个接口—— 解除关联 接口——以向连接管理器表明适配器支持这种优化。当连接暂时超出范围时(即当 bean 或者 servlet 方法退出时),如果连接管理器支持这种优化的话,它就可以解除由应用程序使用的连接句柄与表示物理资源的托管连接的关联。这使托管连接可以返回连接池,为应用程序的其他部分所使用。

惰性关联
这种优化使用 惰性关联,而不是在下次调用方法时重新将连接句柄与托管连接关联。如果方法没有使用连接,或者它只调用连接句柄上不需要访问后端的简单方法,那么托管连接未必会从池中移出。相反,当连接句柄确定它不需要与一个托管连接重新关联时,它就可以将连接管理器强制转换为 LazyAssociatableConnectionManager 并调用 associateConnection 方法。该方法以连接句柄为第一个参数,然后是托管连接厂,以及对 allocateConnection 的第一次调用时传递的请求信息。然后连接管理器从池中找到另一个合适的托管连接,并使用这个托管连接的 associateConnection 方法将它与连接句柄关联。

图 3 显示了这种优化对 图 2 中的 cached-handle 用法的效果。

图 3. 惰性关联降低资源用量
惰性关联降低资源用量

图 3 中虚线箭头表示方法完成时 EJB 容器对连接管理器的通知。这时,托管的连接与连接句柄解除关联,只有当方法试图使用这个句柄时,托管连接才会重新关联。短的绿色线条显示托管连接现在绑定到 EJB 上,以缩短时间并可以在别的地方重新使用。

征募,还是不征募
大家都知道事务代价很高——特别是 XA(全局)事务。这使得让事务不执行非必需的工作变得很重要。JCA 1.5 中的另外两个新接口防止了 XAResource 对象的不必要征募。

我们现在来更详细地分析一下这种增强所针对的问题。假定取用 清单 1 中的 EJB,并用容器托管的事务部署它,将业务方法的事务属性设定为 RequiresNew。调用这个方法时便开始一个新的事务。创建连接时,连接管理器不知道如何使用它,因此它必须从关联的托管连接获得一个 XAResource,并在事务中征募它。连接可能只用于查询数据库或者根本不被使用。但是连接管理器必须征募连接,以备插入或者更新操作。这意味着,资源适配器至少必须进行开始、提交或者回滚,并结束流程返回后端。图 4 展示了这样的事务流程。

图 4. 急切事务征募
急切事务征募

在图 4 中可以看到,获得连接后,XAResource 立即征募到了事务中(即接收一个开始流程)。这意味着当事务方法结束时,流程需要在资源处结束,即使方法没有在事务中使用连接。

如果在事务中涉及另一项资源,就会强制进行不必要的两阶段提交,导致额外的准备流程。这里的惟一可取之处是资源管理器仍然可以从准备调用中返回 XA_RDONLY (用于“read only”)以表明它实际没有做任何工作。事务管理器不需要在那个资源管理器处完成调用,并且如果在事务中只有一个资源管理器真正做了工作,那么事务管理器也许可以避免日志文件中的惰性写操作。

惰性征募
现在您应当认识到除非绝对需要,否则不要在事务中征募。JCA 1.5 规范提供了一个解决方案:惰性征募。清单 4 显示了支持这各种优化所引入的两个接口。

WebSphere 为 JCA 1.5 打下基础
IBM WebSphere 应用服务器 V5 用 JCA 1.0 的一个扩展克服了 cached-handle 连接模式的缺点,它称为 智能连接句柄(smart connection handle)。JCA 1.5 规范通过对清单 3 中的两个接口的修改吸取了这种扩展。

JCA 1.5 在其惰性征募支持中还采用了 WebSphere Application Server V5 的 延迟事务征募 扩展。

请阅读 developerWorks 中 Kevin Kelle 等人撰写的文章以了解更多的 WebSphere 扩展(请参阅 参考资料)。

清单 4. 惰性征募的接口
public interface LazyEnlistableManagedConnection {

}

public interface LazyEnlistableConnectionManager {

    void lazyEnlist(ManagedConnection mc)
            throws ResourceException;

}

LazyEnlistableManagedConnection 接口是由托管连接实现的标记接口,用以向连接管理器表明在事务中创建新的连接或者在连接已经存在的情况下开始一个新的事务时,它不需要将托管连接急切征募到现有事务中。如果连接句柄准备执行应当是任一事务中的一部分的某些工作,并且它的托管连接还没有征募,那么应当确定连接管理器是否实施了 LazyEnlistableConnectionManager 接口。如果实施了,那么它应当调用传递托管连接的 lazyEnlist 方法。这个方法不返回任何结果,但是如果调用线程关联了一个事务,那么这时就征募托管连接的 XAResource。如果连接没有被征募,那么它需要在后面每一项工作之前再次调用 lazyEnlist,以检查在上次调用这个方法之后,是不是没有启动过事务。

图 5 显示了这个新的事件序列。

图 5. 惰性事务征募
惰性事务征募

在图 5 中可以看到,在获得连接时,XAResource 不再急切征募。相反,连接管理器会等待,直到连接用 lazyEnlist 调用表明它要完成一些事务工作时,才会征募 XAResource

什么时候出现错误
JCA 规范的第一个版本提供了一种让资源适配器在连接出现严重错误时通知连接管理器的机制。这是 ConnectionEventListener 接口的 connectionErrorOccurred 方法。收到这个通知后,连接管理器就会毁环发送事件的托管连接,这样就不会再使用它。这些都是不错的。但是,如果到后端的连接丢失了,那么池中的许多托管连接也很有可能不能再使用。

针对这个问题,JCA 1.5 以 ValidatingManagedConnectionFactory 接口的方式引入了一种雅洁的解决方案,如清单 5 所示。

清单 5. 用于确定无效连接的接口
public interface ValidatingManagedConnectionFactory {

    Set getInvalidConnections(Set connectionSet)
            throws ResourceException;

}

由托管连接厂实现的 ValidatingManagedConnectionFactory 接口包含一个方法——getInvalidConnections——它以一组托管连接为参数,返回资源适配器认为无效的一个子集。资源适配器的验证可以采取任何形式,不过通常涉及到对后端的某种“ping”操作,以测试连接。然后连接管理器在资源适配器指示连接错误时调用这个方法,甚至定期调用该方法,以便从池中排除坏掉的连接。

开始和结束
最初的 JCA 版本为托管连接及其相关联的连接句柄提供了详细的生命周期模式,但是它没有为资源适配器提供这种概念。只有当创建了托管连接厂后,部署的资源适配器才知道它的存在。JCA 1.5 中引入的 ResourceAdapter 接口对此进行了纠正,如清单 6 所示。

清单 6. 新资源适配器接口的生命周期方法
public interface ResourceAdapter {

    void start(BootstrapContext ctx)
            throws ResourceAdapterInternalException;

    void stop();

    ...

}

资源适配器可以在其部署描述符(ra.xml)的 resourceadapter-class 元素中给出实现这个接口的类的名称。除了实现 ResourceAdapter 接口,这个类可以通过 JavaBean 样式支持某些属性。与托管连接厂一样,在部署描述符中可以声明这些属性及其默认值,如清单 7 所示。在安装了资源适配器后,应用服务器将允许管理员覆盖这些默认值。

清单 7. 资源适配器部署描述符
<connector>
  ...
  <resourceadapter>
    <resourceadapter-class>
      example.ExampleResourceAdapterImpl
    </resourceadapter-class>
    <config-property>
      <config-property-name>ServerName</config-property-name>
      <config-property-type>java.lang.String</config-property-type>
      <config-property-value>MyServer</config-property-value>
    </config-property>
    <config-property>
      <config-property-name>PortNumber</config-property-name>
      <config-property-type>java.lang.String</config-property-type>
      <config-property-value>1976</config-property-value>
    </config-property>
  </resourceadapter>
  ...
</connector>

在启动时,应用服务器会创建在部署描述符中指定的类的一个实例,并设置管理员提供的属性。这个类必须根据这些属性实现一个 equals 方法,这样应用服务器就可以保证它不会创建一个以上同样的实例。然后会调用 start 方法,向它传递一个实现了 BootstrapContext 接口的对象。可以用这个对象创建计时器、调度其他线程上的工作和控制导入的事务,在本系列的第 2 部分中将更详细地讨论所有这些内容。这个方法将不会堵塞并会及时返回。

应用服务器通常会在关闭或者解除部署资源适配器之前,对资源适配器调用 stop 方法。JCA 1.5 规范描述了这个过程的两个阶段。首先,应用服务器保证依赖资源适配器的所有应用程序都已停止。这可保证程序线程不再使用资源适配器对象,并且所有事务都已完成。然后应用服务器调用 stop 方法。这时,资源适配器将执行一个有序的关闭(例如,释放网络和应用服务器资源,并将所有缓存的数据强行送回后端)。调用 stop 后,应用服务器将不会重新使用资源适配器实例。

为了保留向后兼容性,ManagedConnectionFactory 没有改变,但是如果希望外部资源可以利用资源适配器具有的功能,那么还要实现清单 8 所示的新的 ResourceAdapterAssociation 接口。

清单 8. ResourceAdapterAssociation 接口
public interface ResourceAdapterAssociation {

    ResourceAdapter getResourceAdapter();
    void setResourceAdapter(ResourceAdapter ra)
            throws ResourceException;

}

构建了托管连接厂之后,应用服务器将调用 setResourceAdapter 方法以便将它与其资源适配器关联在一起。在托管连接厂的生命周期内这种关联将会固定下来。这种方法只调用一次。

结束语
本文展示了 JCA 1.5 为现有出站契约带来的四项增强功能。惰性关联和征募优化会提高使用连接的应用程序的性能,验证托管连接厂会改善对故障情况的处理。在资源适配器级别引入生命周期管理为资源适配器提供了多种有趣的新机会。本系列的第 2 部分将分析如何在这个基础上建立新的工作管理和事务流入契约。