EJB与Spring的集成

作为一个轻量级的容器,Spring通常被认为当作是EJB的替代方案。不可否认,在许多应用场合中,Spring以其卓越的性能与丰富的事务处理、ORM、JDBC存取等功能,确实可以取代EJB。不过,我们应当注意到,Spring本身并不排斥EJB,事实上,Spring还为在框架内部存取EJB、实现EJB的功能而提供了良好的支持。而且,如果利用Spring访问EJB所提供的服务,那么这些服务的具体实现可以在本地EJB、远程EJB,或者POJO之间进行透明地切换,而无需改动任何一行客户端的程序代码。

     在本文中,我们着重讨论一下Spring访问、实现EJB的机制,特别是对于无状态会话bean(SLSB)的支持。

 1.如何在Spring中访问EJB?
     在Java EE中,为了调用一个本地或者远程SLSB的方法,客户端必须首先通过JNDI查找获取一个(本地或者远程)EJB Home对象,而后通过这个 EJB Home对象的create()方法获取一个实际的(本地或者远程)EJB对象,从而调用此EJB对象的各种方法。
     为了避免底层代码的重复,许多EJB应用程序采用了Service Locator和Business Delegate设计模式,这比在客户端程序中使用很多JNDI查询要好很多,但其缺陷也是十分明显的,例如:
 *  依赖Service Locator和Business Delegate singleton的程序代码通常是难以测试的。
 *  若只使用Service Locator模式,而不使用Business Delegate,那么应用程序依旧必须调用一个EJB Home的craete()方法,并处理各种异常。因此,应用程序将与EJB的API与复杂编程模型紧密耦合在一起。
 *  使用Business Delegate模式通常将造成大量的重复代码,仅仅为了调用EJB的同一个方法,开发人员必须编写许多程序。

      而通过Spring,则可以创建和使用在Spring内部配置好的代理,这个代理将扮演业务delegate的角色,开发人员无需编写另外一个Service Locator和JNDI查询代码,也无需复制Business Delegate的方法调用,除非是在这些程序代码确实有其他有价值的内容。

     假设有一个需要使用本地EJB的web controller,我们将采用EJB的业务方法接口模式来实现,从而EJB的本地接口可以扩展一个非EJB的业务方法接口。我们将这个业务方法暂且称之为MyComponent,其定义类似于:


public interface MyComponent {
...
}

     使用业务方法接口模式的一个主要原因在于保证本地接口中的方法定义和接口实现bean的方法定义自动同步,另一原因是如果需要将该方法以POJO实现,那么这种转化将比较容易。当然,我们还需要实现本地home接口,并提供一个实现SessionBean和MyComponet业务方法接口的类。现在,为了将示例中的web controller与EJB实现挂钩,我们需要编写的唯一一段代码就是在这个controller中对外公开一个MyComponet对象的setter,示例如下:

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

      然后,便可以在controller中的任何一个业务方法中调用这个myComponet实例。假设需要在Spring容器之外获取该controller对象,我们可以在同一环境中配置一个LocalStatelessSessionProxyFactoryBean 实例作为EJB代理。这个代理的配置以及controller中myComponent属性的设置,均在Spring的配置文件定义,示例如下:

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

     其中,通过myComponent的定义创建了一个EJB代理,而这个EJB将实现特定的业务方法接口。由于EJB的本地home在应用程序启动时就已经被缓存,所以这里只需要一个JNDI查询。每当这个EJB被调用时,EJB代理将调用本地EJB的classname()方法,并调用EJB提供的相应的业务方法。
 这种EJB存取机制极大地简化了应用程序代码,web层代码(抑或其他EJB客户端)将不再依赖EJB的使用。如果我们想将EJB替换为POJO或者其他测试stub,只要把配置文件中的myComponent定义改动一下即可,Java程序依旧保持不变。而且,我们也不必再编写任何JNDI查询代码,或者其他EJB的硬编码。


      实践表明,上述方法(包括对目标EJB的反射式调用)的运行开销是最小的。记住,我们并不想做任何针对EJB的细粒度调用,毕竟这涉及到Java应用服务器中EJB体系架构的成本开销。

       对于远程EJB的访问,本质上与访问本地EJB是相同的,除了需要SimpleRemoteStatelessSessionProxyFactoryBean类。当然,无论是否使用Spring,远程调用的基本语义是适用的。当访问另外一台电脑的虚拟机中某个对象的方法时,这种调用的具体用法和失败处理必须以不同于本地访问的方式处理。

      Spring在EJB客户端支持方面,比非Spring方案具有更多优势。通常,EJB客户端在调用 本地或远程EJB时,很难做自由的前后切换。 这是因为远程接口方法必须抛出RemoteException,并且客户端程序必须处理此异常,但对于本地接口方法的调用则不需要这么做。若将处理本地EJB的客户端程序用来处理远程EJB,那么必须作出修改以增加对远程异常情况的有效处理,同样,若将处理远程EJB的客户端程序用来处理本地EJB,也需要做很多无谓的工作来处理远程异常,或者将异常处理的程序代码删除。不过,当使用Spring远程EJB代理时,开发人员不必在业务逻辑方法接口和具体的EJB实现代码中声明抛出任何RemoteException,并且将有一个统一的远程接口(这个接口抛出RemoteException),并通过这个代理动态地分析客户端调用的是远程EJB还是本地EJB。 也即,客户端程序不需要再处理RemoteException,在调用EJB过程中抛出的任何RemoteException均将被重新以RemoteAccessException类的形式抛出。于是,应用程序可以在本地EJB和远程EJB(甚至是POJO)间任意切换调用,而客户端代码无需关心这些后台的操作。当然,这个解决方案是可选的,用户可以根据自己的意愿在业务接口中声明抛出RemoteExceptions。

2.如何利用Spring实现EJB

      Spring还提供了若干易于使用的类,来帮助开发人员实现EJB。Spring鼓励开发人员将业务逻辑以POJO的形式实现,而将EJB的处理重点放在事务分界与远程调用。
       无论是无状态的会话bean,还是有状态的会话bean,抑或消息驱动的bean,其实现均需分别继承AbstractStatelessSessionBean, AbstractStatefulSessionBean, 和
AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean。
      考虑一个无状态的会话bean的例子。业务接口定义如下:

public interface MyComponent {
   public void myMethod(...);
   ...
}
      我们用一个POJO实现该接口:

public class MyComponentImpl implements MyComponent {
 public String myMethod(...) {
 ...
 }
 ...
}


 最后,定义无状态的会话bean:
 
public class MyComponentEJB extends AbstractStatelessSessionBean
implements MyComponent {

MyComponent myComp;

/**
* 从BeanFactory或者ApplicationContext获取POJO对象
* @参见 org.springframework.ejb.support.AbstractStatelessSessionBean的onEjbCreate()方法
*/
protected void onEjbCreate() throws CreateException {
myComp = (MyComponent) getBeanFactory().getBean(
ServicesConstants.CONTEXT_MYCOMP_ID);
}

// 具体的业务方法,以POJO实现
public String myMethod(...) {
return myComp.myMethod(...);
}
...
}

 AbstractStatelessSessionBean缺省地创建并装载一个Spring IoC容器,这个容器对EJB也是有用处的,例如获取POJO服务对象。容器的装载过程由 BeanFactoryLocator的一个子类完成,缺省情况下使用ContextJndiBeanFactoryLocator来实现。 ContextJndiBeanFactoryLocator从一个资源文件创建ApplicationContext,资源文件的位置由JNDI环境变量指定,例如java:comp/env/ejb/BeanFactoryPath。如果需要更改BeanFactory/ApplicationContext装载策略,那么缺省的BeanFactoryLocator实现可以通过调用setBeanFactoryLocator()方法来重载,这个方法可以在setSessionContext()或者EJB的构造函数中进行调用。
 有状态的会话bean在其生命周期中存在休眠与激活的过程,若此有状态的会话bean使用了一个非序列化的容器实例,由于该bean无法被EJB容器保存,因此它必须从 ejbPassivate()和ejbActivate()分别手工地调用unloadBeanFactory()和loadBeanFactory()方法。为了使用EJB,ContextJndiBeanFactoryLocator类需要装载一个ApplicationContext,这足以一般情况的需要。不过,若ApplicationContext装载很多bean,或者这些bean的初始化可能占用大量时间或内存资源的话,例如初始化Hibernate的一个SessionFactory对象,这个解决方案就存在较大的问题,因为每一个EJB都将会有自己的一个copy。在此情况下,开发人员可以考虑重载缺省的ContextJndiBeanFactoryLocator,并使用BeanFactoryLocator的另一个变体,例如ContextSingletonBeanFactoryLocator类,这样便可以在多个EJB或客户端之间共享同一个容器。示例如下:

/**
* 重载缺省的BeanFactoryLocator实现
* @参见javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext)
*/
public void setSessionContext(SessionContext sessionContext) {
super.setSessionContext(sessionContext);
setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
setBeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID);
}

 而后,创建一个bean定义文件,例如beanRefContext.xml,这个文件将定义EJB可能会用到的所有bean工厂。一般情况下,这个文件将只包含一个bean的定义,例如:

<beans>
<bean id="businessBeanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="businessApplicationContext.xml" />
</bean>
</beans>

 其中 businessApplicationContext.xml文件包含了全部业务POJO对象的定义。ServicesConstants.PRIMARY_CONTEXT_ID则可以定义如下:

public static final String ServicesConstants.PRIMARY_CONTEXT_ID = "businessBeanFactory";


3.EJB与Spring究竟在何时进行集成? 

     尽管Spring和EJB的目标都是为松散耦合的POJO提供企业级服务,但现在很多人都认为Spring+Hibernate的方案,可以充分替代EJB,尤其EJB复杂的编程模型总是令人望而生畏,EJB的部署、运行效率亦总为人诟病。不过,EJB尤其是EJB 3.0在复杂信息系统构建方面,依旧有着Spring目前不能超越的特性。使用EJB 3.0的时候,基于标准的方法、注释的使用、以及与应用程序服务器的紧密集成,实现了厂商无关性,并提高开发效率。尤其是,EJB 3.0在面向事务处理的、尤其是异步通讯环境下的企业级应用方面,比Spring更具备优势。在处理fail-over、load balancing、distributed caching和state duplication、clustering等方面,开发人员在EJB 3.0的环境中不必关心这些问题(当然,在部署时需要考虑)。而在Spring中,需要使用声明式事务服务来管理Hibernate事务,开发人员必须在XML配置文件中显式地配置Spring TransactionManager和Hibernate SessionFactory对象。Spring应用程序开发者必须显式地管理跨多个HTTP请求的事务。此外,要在Spring应用程序中使用群集服务也没有简单的途径。


       EJB与Spring本质上是相互竞争且相互学习的技术,很难说二者的集成是否能够充分彰显二者的优点,弥补彼此的缺陷。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值