技术开发 频道

JCA 1.5: 消息输入流

        部署 MDB

  正如在前面一节看到的,资源适配器支持的每个 MDB 接口都给出实现 ActivationSpec 接口的类的名称,如清单 8 所示:

  清单 8. javax.resource.spi.endpoint.ActivationSpec 接口

1 public interface ActivationSpec extends ResourceAdapterAssociation {
2     void validate() throws InvalidPropertyException;
3 }

  这个实现类遵循 JavaBean 标准,可以通过适当指定的 getter 和 setter 方法来定义大量属性。在将 MDB 部署到应用服务器中时,将使用这个类,如下所示:

  应用服务器通过查看 MDB 部署描述符中 messaging-type 元素的内容,判断 MDB 实现的接口。

  然后应用服务器找到支持这个接口的已经部署的资源适配器,选择一个想在上面部署 MDB 的适配器。

  应用服务器创建 ActivationSpec 类的实例,这个类对应于选中的资源适配器和接口,然后使用内省(introspection)功能确定类的属性以及默认值。

  用 ResourceAdapter 的父类上同名配置的属性覆盖默认值。

  可以选择覆盖这些属性值中的任何值。

  然后这些属性被 MDB 部署描述符中的 activation-config 属性的值再次覆盖。

  应用服务器检测要求在资源适配器部署描述中指定的所有 bean 属性(在这个示例中,是 HostName 和 PortNumber)都有提供的值。

  可以看到,部署的 MDB 最终使用的属性可以来自许多地方,重要的是记住它们的先后顺序。特别是要注意 MDB 的部署描述符不需要包含资源适配器要求的所有属性。

  一旦所有属性设置就绪,应用服务器就可以选择调用 ActivationSpec 上的 validate 方法,或者把这个方法的调用推迟到应用程序启动时。资源适配器可能用这个方法检查算术属性是否在可以接受的范围之内,或者确保表示枚举值的字符串有效。资源适配器还应当检查最后的属性集是否一致;例如,可能要根据其他属性的值来判定某个属性的值是否受限或是必需的。InvalidPropertyException 包含一项或多项检测失败时需要抛出的一组无效属性。

  成功的验证并不意味着部署的 MDB 肯定能成功启动。资源适配器只能执行静态检测,也就是说,执行那些不需要连接到后台系统的检测。只有当应用程序启动时,资源适配器才能验证类似于主机名称和端口号这样的属性是否正确。

  MDB 生命周期管理

  一旦成功地把包含 MDB 的应用程序部署到应用服务器中,下一步就是启动应用程序。应用服务器用清单 9 中的 ResourceAdapter 接口中的两个方法,向资源适配器通知关于某个具体 MDB 生命周期的事件(在规范中称之为 端点):

  清单 9. ResourceAdapter 接口上的端点生命周期方法

1 public interface ResourceAdapter {
2     void endpointActivation(MessageEndpointFactory endpointFactory,
3             ActivationSpec spec) throws ResourceException;
4     void endpointDeactivation(MessageEndpointFactory endpointFactory,
5             ActivationSpec spec);
6     ...
7 }

  在启动应用程序时,要为每个部署的 MDB 调用一次 endpointActivation 方法。第一个参数(我将在下一节详细介绍)是一个创建端点实例的工厂类。方法的第二个参数是在前一节中配置的 ActivationSpec。如果以前没有做过,那么现在可以这样做,此刻,资源适配器通常使用来自 ActivationSpec 的信息与后端系统建立某种形式的远程连接。如果在做这项工作的过程中,资源适配器判定配置的信息不正确,那么它将抛出 NotSupportedException。

  注意,资源适配器不应当阻塞在这个方法中。一旦它确定配置是正确的,则应当立即返回。如果需要进行进一步处理(例如为新的事件定期申请连接),那么资源适配器应当使用 WorkManager 接口,把工作安排到另外一个线程上。

  在包含 MB 的应用程序被停止时,或者处在应用服务器的正常停机期间,会调用对应的 endpointDeactivation 方法。传递给这个方法的参数就是在 endpointActivation 上传递的同一个对象。实际上,因为 JCA 规范强制要求,对于每个端点激活都要创建 MessageEndpointFactory 的一个新实例,所以在不活动(on deactivation)期间传递的对象可以用作在两者之间进行相互关联的键。例如,在激活时创建的资源可能放在一个切断与 MessageEndpointFactory 的联系的映射中,然后在服务器不活动的时候,再检索出这些资源,对它们进行清理。

  调用 MDB

  现在所有工作都已经就绪,到了资源适配器实际调用 MDB 的时候了。在使用 J2EE 1.3 时,这一步可能将使用一个相当麻烦的应用服务器工具,该工具是 JMS 规范的一个组成部分。交互很复杂,而且在某些地方,交互的指定也很糟,从而造成不同的厂商用不同的方式对需求进行解释。幸运的是,JCA 规范不仅让 MDB 能够实现任何接口,还极大地简化了这一过程中的事物。

  清单 10 显示了传递给 endpointActivation 方法的对象实现的 MessageEndpointFactory:

  清单 10. javax.resource.spi.endpoint.MessageEndpointFactory 接口

1 public interface MessageEndpointFactory {
2     boolean isDeliveryTransacted(Method method) throws NoSuchMethodException
3     MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException;
4 }

  isDeliveryTransacted 方法让资源适配器判断关联的 MDB 是否运行在事务中调用指定方法。如果 MDB 正在使用容器托管的事务,而且要处理的方法具有 Required 事务属性,那么 isDeliveryTransacted 会返回 true 。如果事务属性采用 NotSupported 允许的其他值,或者 MDB 正在使用 bean 托管的事务,则返回 false。

  我将在下一节中介绍事务。此刻,假设 isDeliveryTransacted 返回了 false (或者假设资源适配器不支持事务)。这意味着在调用 createEndpoint 方法时,可以把 null 作为 XAResource 的参数传入。这个方法返回一个实现了 MessageEndpoint 接口的对象,如清单 11 所示:

  清单 11. javax.resource.spi.endpoint.MessageEndpoint 接口

1 public interface MessageEndpoint {
2     void release();
3     void beforeDelivery(Method method)
4             throws NoSuchMethodException, ResourceException
5     void afterDelivery() throws ResourceException;
6 }

  返回的对象还实现了 MDB 在它的部署描述符中声明的接口。在最简单的情况下,资源适配器就可以把 MessageEndpoint 的类型转换成必要的接口,然后调用预期方法。

  createEndpoint 返回的对象显然不是应用程序开发人员实现的类的实例,因为这个类没有实现 MessageEndpoint 接口。相反,它是应用服务器创建的一个代理。应用服务器可能使用了 Java 1.4 引入的动态代理支持,即时创建对象,它也可能在部署应用程序时就创建了必要的类。容器使用代理来包装实际的端点,以便提供事务和安全性这样的服务。这里比较一下代理和对无状态 bean 本地接口的引用。在使用会话 bean 的情况下,只能调用在本地接口中声明的方法。类似地,对于代理,也只能调用在 MDB 的部署描述符中声明的接口上的方法。

  资源适配器可以选择保持在一个端点上,以便在后续调用中使用它。或者,资源适配器在完成任务并为下一调用创建另一个端点时,也可以调用 release。通常,应用服务器保持一个端点池,所以第二个选项或许是为了在多个线程之间提供更好的重用而提供的。

  我说过这代表的是简单情况。JCA 规范把这称作 Option A 消息传递。作为何时需要可供替代的 Option B 消息传递的一个例子,可以将资源适配器想像为代表应用程序传输序列化的 Java 对象的消息传递系统的一部分。希望调用的 MDB 方法采用反序列化之后的对象作为参数。糟糕的是,与序列化对象对应的类是 J2EE 程序程序的一部分,因此该类位于应用程序的类路径中,而不是位于资源适配器的类路径中。Option B 用 beforeDelivery 方法和 afterDelivery 方法圆满地解决了这个问题。调用 beforeDelivery 会提前执行一些容器操作(这些操作正常情况下可能是在调用代理和调用实际端点方法之间发生的)。这些操作包括把应用程序的类加载器和线程关联起来。调用 beforeDelivery 之后,资源适配器就可以用线程适配器将对象反序列化,并把对象传递给 MDB 接口上要求的方法。

  容器知道自己已经执行了一些在这个端点代理上要求的操作,所以会在调用实际的 MDB 方法时跳过这些方法。但是,被调用的方法必须和 beforeDelivery 上传递的 Method 对象匹配,否则就会抛出 RuntimeException。在调用该方法之后,资源适配器必须调用 afterDelivery 来执行方法调用之后应当发生的对应的容器操作。例如,这样就允许适配器将方法返回的对象序列化,不过仍然是通过使用应用程序的类加载器来实现的。

  图 1 显示了两个消息传递选项的并列式比较。

  图 1. 消息传递选项

  对于 Option A,可以看到前调用逻辑和后调用逻辑是作为一个方法调用的部分执行的,而对于 Option B,它们被分开,分别由 beforeDelivery 和 afterDelivery 驱动。

0
相关文章