29. 企业JavaBeans(EJB)集成

29.1 简介

作为一个轻量级容器,Spring通常被认为是EJB的替代品。我们相信,对于许多(如果不是大多数)应用程序和用例,Spring作为容器,结合其在事务,ORM和JDBC访问领域的丰富支持功能,是比通过EJB容器和EJB实现等效功能更好的选择。 。

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

在本章中,我们将了解Spring如何帮助您访问和实现EJB。 Spring在访问无状态会话bean(SLSB)时提供了特殊的 Value ,因此我们首先讨论这个问题。

29.2 访问EJB

29.2.1 概念

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

为避免重复的低级代码,许多EJB应用程序使用Service Locator和Business Delegate模式。这些比在整个客户端代码中喷涂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 业务方法接口的实现类。现在,我们需要做的唯一Java编码是将Web层控制器连接到EJB实现,即在控制器上公开类型为 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层代码(或其他EJB客户端代码)不依赖于EJB的使用。如果我们想用POJO或模拟对象或其他测试存根替换这个EJB引用,我们可以简单地更改 myComponent bean定义而不更改一行Java代码。此外,我们不必编写一行JNDI查找或其他EJB管道代码作为我们的应用程序的一部分。

实际应用程序中的基准和经验表明,此方法的性能开销(涉及目标EJB的反射调用)很少,并且在典型使用中通常无法检测到。请记住,我们不希望对EJB进行细粒度调用,因为应用程序服务器中的EJB基础结构会产生相关成本。

关于JNDI查找有一点需要注意。在bean容器中,这个类通常最适合用作单例(没有理由将它作为原型)。但是,如果该bean容器预先实例化单例(就像各种XML ApplicationContext 变体一样),如果在EJB容器加载目标EJB之前加载bean容器,则可能会出现问题。这是因为JNDI查找将在此类的 init() 方法中执行,然后进行缓存,但EJB尚未绑定到目标位置。解决方案是不预先实例化此工厂对象,但允许在首次使用时创建它。在XML容器中,这是通过 lazy-init 属性控制的。

虽然大多数Spring用户不会对此感兴趣,但那些使用EJB进行编程AOP的人可能希望查看 LocalSlsbInvokerInterceptor

29.2.3 访问远程SLSB

除了使用 SimpleRemoteStatelessSessionProxyFactoryBean<jee:remote-slsb> 配置元素之外,访问远程EJB与访问本地EJB基本相同。当然,无论有没有Spring,远程调用语义都适用;对另一台计算机中另一台虚拟机中的对象的方法调用有时必须在使用场景和故障处理方面区别对待。

Spring的EJB客户端支持比非Spring方法增加了一个优势。通常,EJB客户端代码在本地或远程调用EJB之间来回切换是很有问题的。这是因为远程接口方法必须声明它们抛出 RemoteException ,而客户端代码必须处理这个,而本地接口方法则不然。为需要移动到远程EJB的本地EJB编写的客户端代码通常必须进行修改以添加对远程异常的处理,并且为需要移动到本地EJB的远程EJB编写的客户端代码可以保持不变但是很多不必要的远程异常处理,或者需要修改以删除该代码。使用Spring远程EJB代理,您可以不在业务方法接口中声明任何抛出的 RemoteException 并实现EJB代码,具有相同的远程接口,除了它抛出 RemoteException ,并依赖代理动态处理这两个接口好像他们是一样的。也就是说,客户端代码不必处理已检查的 RemoteException 类。在EJB调用期间抛出的任何实际 RemoteException 将被重新抛出为未选中 RemoteAccessException class,它是 RuntimeException 的子类。然后,可以在本地EJB或远程EJB(甚至普通Java对象)实现之间随意切换目标服务,而无需客户端代码知道或关心。当然,这是可选的;没有什么可以阻止你在业务界面中声明 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> 工具)在运行时透明地适应实际组件。如果找到(EJB 2.x样式),它们将处理home接口,或者如果没有可用的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组件类中的Spring的 @Autowired 注释: org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor 。此拦截器可以通过EJB组件类中的 @Interceptors 注释应用,也可以通过EJB部署描述符中的 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’sgetBeanFactory方法,例如,从自定义持有者类中获取共享ApplicationContext` 。

Updated at: 5 months ago
28.10.3. 异步RestTemplateTable of content30. JMS(Java消息服务)
Comment
You are not logged in.

There are no comments.