29. 企业 JavaBeans(EJB)集成

29.1 Introduction

作为轻量级容器,Spring 通常被认为是 EJB 的替代品。我们确实相信,对于很多(如果不是大多数)应用程序和用例,将 Spring 作为容器,再加上其在事务,ORM 和 JDBC 访问方面的丰富支持功能,相较于通过 EJB 容器和 EJB 实现等效功能是更好的选择。 。

但是,请务必注意,使用 Spring 不会阻止您使用 EJB。实际上,Spring 使访问 EJB 以及在其中实现 EJB 和功能变得更加容易。另外,使用 Spring 访问 EJB 提供的服务可以使这些服务的实现稍后在本地 EJB,远程 EJB 或 POJO(普通旧 Java 对象)变体之间透明切换,而不必更改 Client 端代码。

在本章中,我们将研究 Spring 如何帮助您访问和实现 EJB。当访问 Stateless 会话 Bean(SLSB)时,Spring 提供了特殊的价值,因此我们将从讨论这一点开始。

29.2 访问 EJB

29.2.1 Concepts

要在本地或远程 Stateless 会话 Bean 上调用方法,Client 端代码通常必须执行 JNDI 查找以获取(本地或远程)EJB Home 对象,然后对该对象使用“ create”方法调用以获取实际的(本地)或远程)EJB 对象。然后在 EJB 上调用一种或多种方法。

为了避免重复的低级代码,许多 EJB 应用程序都使用服务定位器和业务委托模式。这比在整个 Client 端代码中喷洒 JNDI 查找要好,但是它们的常规实现有很多缺点。例如:

  • 通常,使用 EJB 的代码取决于 Service Locator 或 Business Delegate 单例,使其难以测试。

  • 在不使用业务委托的情况下使用服务定位器模式的情况下,应用程序代码仍然最终必须在 EJB 主目录上调用 create()方法,并处理产生的异常。因此,它仍然与 EJB API 和 EJB 编程模型的复杂性联系在一起。

  • 实现业务委托模式通常会导致大量的代码重复,我们必须编写许多方法来简单地在 EJB 上调用相同的方法。

Spring 的方法是允许创建和使用通常在 Spring 容器内配置的代理对象,这些代理对象充当无代码业务委托。除非您实际在此类代码中增加了实际价值,否则您无需在手动编码的业务委托中编写其他服务定位符,其他 JNDI 查找或重复方法。

29.2.2 访问本地 SLSB

假设我们有一个需要使用本地 EJB 的 Web 控制器。我们将遵循最佳实践,并使用 EJB 业务方法接口模式,以便 EJB 的本地接口扩展非特定于 EJB 的业务方法接口。我们将此业务方法界面称为MyComponent

public interface MyComponent {
    ...
}

使用业务方法接口模式的主要原因之一是确保本地接口中的方法签名与 bean 实现类之间的同步是自动的。另一个原因是,如果有必要的话,以后可以使我们更轻松地切换到服务的 POJO(普通旧 Java 对象)实现。当然,我们还需要实现本地 home 接口,并提供实现SessionBeanMyComponent业务方法接口的实现类。现在,将 Web 层控制器连接到 EJB 实现所需的唯一 Java 编码是在控制器上公开MyComponent类型的 setter 方法。这会将引用另存为控制器中的实例变量:

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
    this.myComponent = myComponent;
}

随后,我们可以在控制器中的任何业务方法中使用此实例变量。现在假设我们从 Spring 容器中获取控制器对象,我们可以(在相同上下文中)配置LocalStatelessSessionProxyFactoryBean实例,该实例将是 EJB 代理对象。代理的配置以及控制器的myComponent属性的设置是通过以下配置项完成的:

<bean id="myComponent"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/myBean"/>
    <property name="businessInterface" value="com.mycom.MyComponent"/>
</bean>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

尽管没有强迫您使用 AOP 概念来欣赏结果,但仍有许多工作在幕后进行,这要归功于 Spring AOP 框架。 myComponent bean 定义为 EJB 创建了一个代理,该代理实现了业务方法接口。 EJB 本地主目录在启动时被缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会在本地 EJB 上调用classname方法,并在 EJB 上调用相应的业务方法。

myController bean 定义将控制器类的myComponent属性设置为 EJB 代理。

另外(最好是在有许多这样的代理定义的情况下),请考虑在 Spring 的“ jee”名称空间中使用<jee:local-slsb>配置元素:

<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
        business-interface="com.mycom.MyComponent"/>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

这种 EJB 访问机制极大地简化了应用程序代码:Web 层代码(或其他 EJBClient 端代码)不依赖 EJB 的使用。如果我们想用 POJO 或模拟对象或其他测试存根替换该 EJB 引用,我们可以简单地更改myComponent bean 定义而无需更改任何 Java 代码。此外,作为应用程序的一部分,我们不必编写任何一行 JNDI 查找或其他 EJB 管道代码。

实际应用中的基准和经验表明,这种方法的性能开销(涉及目标 EJB 的反射调用)是最小的,通常在典型使用中是无法检测到的。请记住,无论如何我们都不希望对 EJB 进行细粒度的调用,因为与应用程序服务器中的 EJB 基础结构相关的成本很高。

关于 JNDI 查找有一个警告。在 bean 容器中,通常最好将此类用作单例(根本没有理由使其成为原型)。但是,如果该 Bean 容器预先实例化了单例(与各种 XML ApplicationContext变体一样),那么在 EJB 容器加载目标 EJB 之前加载 Bean 容器时,您可能会遇到问题。这是因为将在此类的init()方法中执行 JNDI 查找,然后将其缓存,但是 EJB 尚未绑定在目标位置。解决方案是不预先实例化该工厂对象,而是允许在首次使用时创建它。在 XML 容器中,这是通过lazy-init属性控制的。

尽管大多数 Spring 用户对此并不感兴趣,但是那些使用 EJB 进行编程 AOP 的用户可能希望查看LocalSlsbInvokerInterceptor

29.2.3 访问远程 SLSB

除了使用SimpleRemoteStatelessSessionProxyFactoryBean<jee:remote-slsb>配置元素外,访问远程 EJB 与访问本地 EJB 基本相同。当然,无论是否使用 Spring,都将应用远程调用语义。在使用情况和故障处理方面,有时必须以不同的方式对待对另一台计算机中另一台 VM 中的对象上的方法的调用。

与非 Spring 方法相比,Spring 的 EJBClient 端支持增加了另一个优势。通常,在本地或远程调用 EJB 之间轻松地来回切换 EJBClient 端代码是有问题的。这是因为远程接口方法必须声明它们抛出RemoteException,而 Client 端代码必须对此进行处理,而本地接口方法则不需要。通常需要修改为本地 EJB 编写的需要移动到远程 EJB 的 Client 端代码,以添加对远程异常的处理,为远程 EJB 编写的需要移动到本地 EJB 的 Client 端代码可以保持不变,但是可以许多对远程异常的不必要处理,或者需要进行修改以删除该代码。使用 Spring 远程 EJB 代理,您不能在业务方法接口和实现 EJB 代码中声明任何引发的RemoteException,而是具有一个完全相同的远程接口,只是它确实抛出RemoteException,并且依靠代理来动态地处理这两个接口好像他们是一样的。也就是说,Client 端代码不必处理已检查的RemoteException类。在 EJB 调用期间抛出的任何实际RemoteException都将重新抛出为未经检查的RemoteAccessException类,该类是RuntimeException的子类。然后,可以在本地 EJB 或远程 EJB(甚至纯 Java 对象)实现之间随意切换目标服务,而无需了解或关心 Client 端代码。当然,这是可选的。没有什么可以阻止您在业务界面中声明RemoteExceptions

29.2.4 访问 EJB 2.x SLSB 与 EJB 3 SLSB

通过 Spring 访问 EJB 2.x 会话 Bean 和 EJB 3 会话 Bean 在很大程度上是透明的。 Spring 的 EJB 访问器(包括<jee:local-slsb><jee:remote-slsb>设施)在运行时透明地适应实际组件。它们处理 Home 接口(如果找到)(EJB 2.x 样式),或者在没有 Home 接口可用时(EJB 3 样式)执行直接组件调用。

注意:对于 EJB 3 会话 Bean,您也可以有效地使用JndiObjectFactoryBean/<jee:jndi-lookup>,因为公开了完全可用的组件引用以用于在那里的普通 JNDI 查找。定义明确的<jee:local-slsb>/<jee:remote-slsb>查找仅提供一致且更明确的 EJB 访问配置。

29.3 使用 Spring 的 EJB 实现支持类

29.3.1 EJB 3 注入拦截器

对于 EJB 3 会话 Bean 和消息驱动的 Bean,Spring 提供了一个方便的拦截器,用于解析 EJB 组件类org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor中的 Spring 的@AutowiredComments。可以通过 EJB 组件类中的@InterceptorsComments 或通过 EJB 部署 Descriptors 中的interceptor-binding XML 元素来应用此拦截器。

@Stateless
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class MyFacadeEJB implements MyFacadeLocal {

    // automatically injected with a matching Spring bean
    @Autowired
    private MyComponent myComp;

    // for business method, delegate to POJO service impl.
    public String myFacadeMethod(...) {
        return myComp.myMethod(...);
    }

    ...

}

缺省情况下,SpringBeanAutowiringInterceptorContextSingletonBeanFactoryLocator获取目标 bean,上下文在名为beanRefContext.xml的 bean 定义文件中定义。默认情况下,期望使用单个上下文定义,该上下文定义是通过类型而不是名称来获取的。但是,如果需要在多个上下文定义之间进行选择,则需要特定的定位器键。可以通过覆盖自定义SpringBeanAutowiringInterceptor子类中的getBeanFactoryLocatorKey方法来显式指定定位器键(即beanRefContext.xml中的上下文定义的名称)。

或者,考虑覆盖SpringBeanAutowiringInterceptor's `getBeanFactory方法,例如从自定义所有者类中获取共享的ApplicationContext