UML软件工程组织

如何在EJB应用程序中使用Spring
Matrix

在传统J2EE应用程序中,企业JavaBeans(EJB)构成了应用程序架构的基础。尽管Spring提供了EJB所提供的许多服务的简化版本,例如可申明的事务管理和对象持久性,但是,在一段时间内,许多应用程序将会继续使用EJB来构建。幸运的是,你能够在基于EJB的解决方案中使用Spring,本文是从Pro Spring中摘录,Pro Spring的作者Rob Harrop和Jan Machacek主要研究在Spring中,如何简化EJB的创建,以及Spring如何提供一个简单,友好的方法来访问EJB资源。

随着Spring的发展,现在开发者第一次真正有了一个EJB的轻量级代替者。使用Spring,你能够利用许多EJB所提供的特性,例如可申明的事务管理,对象池和简单的ORM(对象角色建模)等功能。这就是说,在可预见的未来,我们期望EJB继续能够用于应用程序的开发中。尽管在本书中,我们并没有考虑使用或者不使用EJB的原因,但是,从我们在Spring方面出色的经验来看,我们推荐你尽可能的使用Spring来代替EJB。有关Spring和EJB优点和缺点更加详细的讨论,可以从Rod Johnson和Juergen Hoeller所写的书《Expert One-on-One J2EE without EJB》(Wrox, 2004)中得到。在本文中,我们将要重点讨论的是如何将Spring技术应用在使用EJB构建的系统中。

Spring中对EJB的支持

Spring对EJB支持能够粗略的分成两类:访问和实现。Spring中的访问支持类使你能够容易的访问EJB资源。在这一节里,我们研究Spring如何扩展基本的JNDI(Java名字目录接口)支持框架对EJB的访问,并且利用AOP(面向切面的编程)支持来提供对EJB资源的基于代理访问。

Spring提供了抽象基类来支持EJB的实现,这使得创建无状态EJB,有状态EJB,消息驱动EJB,这三种类型的EJB更加简单。这三种类的使用前提并不是减轻创建EJB的负担,而是让你能够从bean内部比较容易的访问Spring管理的资源,更重要的是,这有助于将业务逻辑从EJB实现中分离,写到EJB的POJO(plain old Java object)对象中。在这里,不用担心这些模糊说法,我们将会在下一章中,使用两个例子来详细地讨论他们。

我们将要构建一个使用了两个EJB服务的简单的Web应用程序。第一个无状态会话bean实现了EchoService业务逻辑接口,并且提供了简单的响应能力。第二个是有状态会话bean,它实现了CounterService业务逻辑接口,并且提供了有状态计数服务。

这些是简单的例子,但是它们有助于演示所推荐的Spring中编译EJB组件的解决办法,和Spring中支持EJB的各种不同组件之间的区别。除了讨论例子中的多种部署描述符外,我们并不对EJB作更详细的研究。然而,我们将对Spring在多种组件中对EJB支持的实现,以及这种实现如何影响应用程序作深入的研究。特别地,我们研究Spring如何在ApplicationContext中定位EJB,以及如何使用JNDI基础框架来定位JNDI资源。

你可能注意到,我们提到的Spring支持三种EJB,但是我们仅仅实现两种类型的EJB,无状态和有状态。消息驱动bean的支持类使用了类似于无状态和有状态会话bean所使用的模式。

在Spring中构建EJB组件

Spring提供了三种抽象类作为EJB bean类的基类,他们是:AbstractStatelessSessionBean,AbstractStatefulSessionBean和AbstractMessageDrivenBean。当使用Spring来构建EJB的时候,必须提供所有不同的接口和home类,但是,当实现bean类的时候,就需要从合适的Spring基类继承。Spring提供的基类允许EJB组件访问Spring的ApplicationContext,并且,这样允许这些组件访问Spring所管理的资源。

在继续使用Spring来构建EchoService和CounterService bean的详细讨论之前,我们将要看看,Spring如何在ApplicationContext中定位EJB组件,以及在使用Spring时,所推荐编译EJB解决办法。

Spring中EJB类层次

Spring提供了定义良好的EJB支持类的类层次,如图1所示。

  图1.Spring中的EJB支持类

正如你看到的那样,中心的基类AbstractEnterpriseBean暴露了beanFactoryLocator属性来允许子类访问正在使用的实例BeanFactoryLocator。在下一节中,将通过loadBeanFactory()和unloadBeanFactory()这两个方法的使用来更加详细的讨论BeanFactoryLocator接口。注意到AbstractStatelessSessionBean类已经实现了ejbCreate(),ejbActivate()和ejbPassivate()方法,而类AbstractStatefulSessionBean并没有实现这些方法。Spring对钝化bean有特别的要求,我们将在后面章节“编译有状态会话bean”中更加详细的讨论这种bean。

接口BeanFactoryLocator

Spring能够从ApplicationContext中装载所管理的资源,Spring中的EJB基类的主要特征就是能够访问ApplicationContext来装载资源。这种功能不是基类本身提供的,而是被委托给BeanFactoryLocator接口的实现,像显示在列表1中的情形一样。

列表1. BeanFactoryLocator接口

public interface BeanFactoryLocator {
   BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException;
}

当Spring不能控制资源的创建,也不能自动配置资源的情况下,就需要使用BeanFactoryLocator接口。在这些情况下,BeanFactoryLocator接口允许用一种外部可配置的方式在BeanFactory中配置资源。当然,在这样的情况下,BeanFactory配置能够简单地托管资源,但是,那意味着,作为应用程序开发员的你,不能控制应用程序。使用BeanFactoryLocator,你就能够完全控制用于配置的BeanFactory或者ApplicationContext如何定位EJB组件。

注意到,BeanFactoryLocator并不直接返回BeanFactory实例;而是返回一个BeanFactoryReference。BeanFactoryReference是一个轻量级BeanFactory或ApplicationContext的包装器,这个包装器允许使用BeanFactory的资源平滑地释放对BeanFactory的引用。对于BeanFactoryLocator的实现和BeanFactory或者ApplicationContext接口来说,这个接口BeanFactoryReference的实际的实现是特别的。当我们在列表13中研究状态会话bean如何释放对BeanFactoryReference的引用挂起bean时,我们将再对这个引用功能进行研究。

缺省情况下,所有EJB基类使用了BeanFactoryLocator的ContextJndiBeanFactoryLocator实现。本质上来说,这个类在一个给定JNDI地点上查找一个逗号分隔的配置文件列表,并且使用这些配置文件来创建ClassPathXmlApplicationContext的实例。三个基本EJB类都通过AbstractEnterpriseBean类表现出属性beanFactoryLocator,通过设置这个属性,就能够提供你自己对于BeanFactoryLocator的不同实现。然而,如果你这样做了,那么,请注意,你的bean的每一个实例都有它自己的ContextJndiBeanFactoryLocator实例,同样,每个ContextJndiBeanFactoryLocator的实例都有它自己的ClassPathXmlApplicationContext实例。

虽然为你的EJB实例创建的所有ApplicationContext实例都用同样的方式配置的,但是,所配置的bean是不一样的。考虑一下定义了echoService bean的Spring配置,其中,EchoServiceEJB将使用echoService。如果你的应用程序服务器创建100EJB个实例,那么,也创建了100个ContextJndiBeanFactoryLocator的实例,以及100个ClassPathXmlApplicationContext和echoService bean的实例

如果应用程序不希望发生这种情况,那么Spring提供SingletonBeanFactoryLocator和ContextSingletonBeanFactoryLocator类来分别装载BeanFactory和ApplicationContext的单件实例。有关他们更多信息,参见这些类的Javadoc。

Spring对于EJB的解决方案

EJB一个最大的缺点就是要想脱离EJB容器而单独测试EJB组件非常困难,这使得EJB实现的业务逻辑的单元测试成为只有那些受虐狂者才愿意做的事。然而,这个问题的解决方法已经出现了一段时间了,它包括在POJO中实现与EJB bean类同样的业务逻辑,然后,就能将bean类委托给POJO。在Spring中,由于不必将如何定位和创建POJO的实现的任何逻辑嵌入到EJB中,这种实现就是非常简单,和灵活的。图2显示了在构建EchoService EJB的时候,如何使用这种方法的。

这里,正如所期望的那样,你看到了bean类EchoServiceEJB实现了接口EchoService,并且注意到EchoServiceEJB依赖于接口EchoService,有一个EchoService类型的私有成员。

构建一个无状态会话bean

无状态会话bean是用Spring最容易构建的EJB;这是因为无论在何种情况下,它都不需要特殊的处理,并且类AbstractStatelessSessionBean实现了所有ejbXXX()方法。

正如列表2所示,我们创建了服务接口。

列表2. EchoService接口

package com.apress.prospring.ch13.ejb;
public interface EchoService {
   public String echo(String message);
}

注意到,服务接口不是与EJB相关的,甚至,在实现EJB的时候并不要求这个服务接口。EJB开发中传统办法是在EJB相关的本地和远程接口中定义业务逻辑方法。在这种办法中,在标准Java接口中定义业务方法,然后本地和远程bean接口扩展这个标准接口。这种服务接口不仅给本地和远程接口提供了扩展的标准接口,而且也提供了EJB bean类和POJO实现类能够实现的通用的接口。虽然我们并不推荐EJB bean类不要实现本地或者远程接口,但是实现了这个服务接口的EJB bean也不会有什么问题。通过使所有EJB的组成组件共享通用的接口,就能够很容易的确保本地接口定义你想要调用的方法,这样,你的bean类实现了本地接口需要的方法,而POJO实现EJB bean类需要的方法。

下一步是创建bean接口。在这个例子中,我们并不在远程容器中使用EJB,因此,我们一直使用本地接口,正如列表3所示。

列表3. EchoService EJB的本地接口

package com.apress.prospring.ch13.ejb;
import javax.ejb.EJBLocalObject;
public interface EchoServiceLocal extends EchoService, EJBLocalObject {
}

注意到,像前面讨论的那样,这个接口并没有定义业务逻辑的方法。相反,EchoServiceLocal接口扩展了服务接口EchoService。

接下来,需要创建EJB Home接口。在这个例子中,我们并不会远程调用EJB,因此,我们一直使用在列表4中展现的本地home接口。

列表4. EchoService EJB的本地home接口

package com.apress.prospring.ch13.ejb;
import javax.ejb.CreateException;
import javax.ejb.EJBLocalHome;
public interface EchoServiceHome extends EJBLocalHome {
   public EchoServiceLocal create() throws CreateException;
}

仔细的看看大多数EJB规范要求的样板代码。

当你使用传统的架构来构建EJB的时候,下一步就是创建bean类。然而,我们将要把EchoService的实现构建成一个POJO,然后,将EJB bean类的实现委托给POJO实现。列表5展现了EchoService接口的POJO实现。

列表5. EchoService的POJO实现

package com.apress.prospring.ch13.ejb;
public class EchoServiceImpl implements EchoService {
   public String echo(String message) {
      return message;
   }
}

现在所有EchoService的实现都包含在一个POJO中了,你能够在EJB容器外部容易地测试这个POJO。(注意,这个实现需要很多测试!)

实现EchoService EJB的最后一步是创建bean类自身。这是开始使用Spring的地方。通过在EchoServiceImpl中对EchoService接口的真正实现,就能够简单地选择创建EchoServiceEJB的一个实例。

然而,如果想要改变这个实现,会出现什么事情呢?需要重新译和部署EJB。使用Spring,我们能够从ApplicationContext中装载实现类和任何依赖类。这就意味着,你能够完全的利用实际实现类的Spring的特征,包括DI,AOP和扩展配置支持。列表6展现了EchoService bean类的实现。

列表6. EchoServiceEJB类

package com.apress.prospring.ch13.ejb;
import javax.ejb.CreateException;
import org.springframework.ejb.support.AbstractStatelessSessionBean;
public class EchoServiceEJB extends AbstractStatelessSessionBean implements EchoService {
   private static final String BEAN_NAME = "echoService";
   private EchoService service;
   public String echo(String message) {
      return service.echo(message);
   }
   protected void onEjbCreate() throws CreateException {
      service = (EchoService) getBeanFactory().getBean(BEAN_NAME);
   }
}

在这个代码中,有一些值得注意的让人感兴趣的地方。首先,你应该注意到EchoServiceEJB继承了Spring基类AbstractStatelessSessionBean并且实现了EchoService接口。其次,所有EchoService接口上方法都被委托给EchoService类包装实现。第三,这个bean没有ejbXXX()等方法,这些方法是实现在AbstractStatelessSessionBean中。最后,注意onEjbCreate()方法。这是一个AbstractStatelessSessionBean提供的钩子方法,并且在ejbCreate()中调用。这个方法能够获得EJB bean需要的任何Spring管理的资源,特别地,能够获得服务接口真正的实现。正如你所看到的那样,我们从Spring中获得echoService bean并且把它保存在service成员中。这个bean是EchoService实现,并且EchoServiceEJB将功能委托给这个bean。

还记得在图1中,getBeanFactory()方法定义在AbstractEnterpriseBean类之上,而AbstractEnterpriseBean类构成了所有SpringEJB实现支持类的基础。缺省情况下,在ContextJndiBeanFactoryLocator实例中查找AbstractEnterpriseBean类,并且从getBeanFactory()方法返回ApplicationContext来装载它。ContextJndiBeanFactoryLocator通过查找一个特定的文件名列表JNDI位置来查找AbstractEnterpriseBean类,并且从这个文件列表中装载ApplicationContext配置。我们能够在部署描述符中指定这个EJB,如列表7显示那样。

列表7.在部署描述符中配置ContextJndiBeanFactoryLocator

<session>
   <description>Echo Service Bean</description>
   <ejb-name>EchoServiceEJB</ejb-name>
   <local-home>com.apress.prospring.ch13.ejb.EchoServiceHome</local-home>
   <local>com.apress.prospring.ch13.ejb.EchoServiceLocal</local>
   <ejb-class>com.apress.prospring.ch13.ejb.EchoServiceEJB</ejb-class>
   <session-type>Stateless</session-type>
   <transaction-type>Container</transaction-type>
   <env-entry>
      <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>applicationContext.xml</env-entry-value>
   </env-entry>
</session>

在这个部署描述符中的重要部分是 标签,它设置了ejb/BeanFactoryPath JNDI位置为applicationContext.xml。JNDI路径ejb/BeanFactoryPath是ContextJndiBeanFactoryLocator查找配置文件路径缺省位置。在列表7中的配置实质上是告诉与EchoServiceEJB相关联的实例ContextJndiBeanFactoryLocator创建一个ApplicationContext,这个ApplicationContext是使用文件applicationContext.xml中的详细细节来配置的。

一个需要注意的重要地方是这个配置文件是和EJB相关的,并且与任何主应用程序可能有的applicationContext.xml文件相分离的。当打包应用程序时,要把与EJB相关的applicationContext.xml文件放到EJB的jar文件中,然后将web应用程序的applicationContext.xml文件放到war文件中。要完成EchoServiceEJB的部署,我们需要和Jboss相关的bean部署描述符。显示在列表8中。

列表8. JBoss部署描述符

<jboss>
   <enterprise-beans>
      <session>
         <ejb-name>EchoServiceEJB</ejb-name>
         <local-jndi-name>ejb/echoService</local-jndi-name>
      </session>
   </enterprise-beans>
</jboss>

这个部署描述符中的重要部分是 标签,它告知Jboss绑定EJB home的JNDI名字。在这种情况下,我们便能够在ejb/echoService下找到EchoServiceHome。

这些都是对与无状态会话bean的开发过程。正如你所看到的那样,与传统的EJB实现的方法相比,实现步骤并没有太多的不同。然而,使用这儿的解决办法,不需要依赖EJB容器,就能够容易地测试你的业务逻辑。通过使用Spring,我们使基于POJO的实现方法具有了更多的灵活性,并且,能够容易地避免将EJB bean与特定的POJO实现结合在一起。

构建有状态会话bean

构建有状态会话bean比构建无状态会话bean更加复杂,因为在构建有状态会话bean时,必须考虑在bean处于挂起状态时,ApplicationContext将会出现什么情况。回顾图1,AbstractStatefulSessionBean并没有实现ejbCreate(),ejbActivate()和ejbPassivate()。这是因为Spring提供的BeanFactory和ApplicationContext的缺省实现是不可串行化的,因而,有状态会话bean就不能够被(passivated)钝化处理。

为了解决这种情况,有两种选项。最简单的选项是实现ejbActivate()和ejbPassivate()来在适当的时候装载和卸载ApplicationContext,这就允许不需要存储ApplicationContext就能够实现bean的(passivated)钝化,并且能够在bean被重新激活的时候重构ApplicationContext。第二个选项是提供你自己的BeanFactoryLocator实现,它创建了一个可串行化的BeanFactory或者ApplicationContext。当使用第一种方法时,注意到当使用ContextJndiBeanFactoryLocator的时候,bean的激活就导致ApplicationContext被随机地载入。如果对于你的应用程序来说,这是不可接受的开销,那么,你可以像前面讨论的那样,使用ContextSingletonBeanFactoryLocator来代替ContextJndiBeanFactoryLocator。

就像前面那样,要开始构建bean,我们将从服务接口开始讨论,如列表9显示的那样。

列表9. CounterService接口

package com.apress.prospring.ch13.ejb;
public interface CounterService {
   public int increment();
   public int decrement();
}

我们创建一个基本的服务接口,这个接口是被本地接口,bean接口,和实现类所实现。下一步是创建本地接口,显示在列表10中。

列表10. CounterService的本地接口

package com.apress.prospring.ch13.ejb;
import javax.ejb.EJBLocalObject;
public interface CounterServiceLocal extends CounterService, EJBLocalObject {
}
 

本地接口自己不包含方法定义,所有的业务方法都是从CounterService接口继承的。下一步,我们需要创建home接口,如列表11所示。

列表11. CounterService的本地home接口

package com.apress.prospring.ch13.ejb;
import javax.ejb.CreateException;
import javax.ejb.EJBLocalHome;
public interface CounterServiceHome extends EJBLocalHome {
   public CounterServiceLocal create() throws CreateException;
}

这个实现没有什么特别的地方,它只是标准EJB的解决办法。

CounterService EJB需要的第四个组件是POJO实现。这个实现是有状态的,并且被标记成,因此,它能够和CounterServiceEJB一起被钝化(passivated)。列表12显示了CounterServiceImpl类。

列表12.基本的CounterService实现

package com.apress.prospring.ch13.ejb;
import java.io.Serializable;
public class CounterServiceImpl implements CounterService, Serializable {
   private int count = 0;
   public int increment() {
      return ++count;
   }
public int decrement() {
      return --count;
   }
}

在现在为止,我们所看到的有状态会话bean的实现类似于无状态会话bean的实现。然而,当实现bean类的时候,就存在显著的区别。列表13显示了CounterServiceEJB的bean类。

列表13. CounterServiceEJB类

package com.apress.prospring.ch13.ejb;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import org.springframework.ejb.support.AbstractStatefulSessionBean;
public class CounterServiceEJB extends AbstractStatefulSessionBean implements CounterService {
   private CounterService service;
   public int increment() {
      return service.increment();
   }
   public int decrement() {
      return service.decrement();
   }
   public void ejbCreate() throws CreateException {
      load();
      service = (CounterService) getBeanFactory().getBean("counterService");
   }
   public void ejbActivate() throws EJBException, RemoteException {
      load();
      service = (CounterService) getBeanFactory().getBean("counterService");
   }
   public void ejbPassivate() throws EJBException, RemoteException {
      unload();
}
   private void load() {
      loadBeanFactory();
   }
   private void unload() {
      unloadBeanFactory();
      setBeanFactoryLocator(null);
   }
}
 

Bean类实现的第一部分类似于前面创建的EchoServiceEJB的实现。然而,显著的区别在于方法ejbCreate(),ejbActivate()和ejbPassivate()中。在ejbCreate()中,我们调用方法load(),相应地它调用了在AbtstractEnterpriseBean上的loadBeanFactory()方法。这就使得BeanFactoryLocator的实现载入BeanFactory,并且能够调用方法getBeanFactory()。最后,方法ejbCreate()使用BeanFactory来访问counterService bean,并把这个bean存储在成员service中。

现在,bean已经被配置了,并且将会一直运行,直到容器将它挂起。当容器要挂起bean的时候,就调用方法ejbPassivate(),然后是方法unload()。方法unload()所进行的第一步操作是调用unloadBeanFactory(),它清除了被ContextJndiBeanFactoryLocator载入的ApplicationContext,然后将引用设置为null。正如前面所提到的那样,因为ClassPathXmlApplicationContext(ContextJndiBeanFactoryLocator所使用的ApplicationContext实现)是不可串行化的,所以,它不能和bean一起被钝化(passivated)。最后,unload()删除所有关于BeanFactoryLocator实现的引用,因为与ClassPathXmlApplicationContext一样,ContextJndiBeanFactoryLocator是不可串行化的。

当容器在钝化(passivation)一个bean之后重新激活它的时候,它就调用在bean上的方法ejbActivate(),以使得bean能够重新构建任何没有被钝化(passivated)的状态。在这个例子中,我们简单地重新调用load()来重载ApplicationContext。注意到,如果你正在使用一个定制的BeanFactoryLocator实现。那么你也需要在ejbActivate()中重新实例化它。一旦ApplicationContext被重新载入,我们就从ApplicationContext中重新装载了counterService bean。

你可以仔细的思考为什么要在ejbCreate()中重新载入ApplicationContext。由于类是可串行化的,它将要和bean一起被钝化(passivated),所以,一旦得到了counterService bean,所要做的就是关闭BeanFactory。然而,需要记住的就是你能够在ApplicationContext中配置任何CounterService的实现,包括不可以串行化的CounterService实现。如果你能够保证所有实现都是可串行化的,那么你就能够采用这种方法,或者在ejbCreate()中进行检查以确保实现是真正可串行化的。如果不能保证实现是可串行化的,就必须假定它是不可串行化的,因此,必须在钝化(passivation)的时候卸载BeanFactory,在激活的时候载入它。

与无状态会话bean一样,我们需要在EJB部署描述符中定义ApplicationContext的配置位置,如列表14显示的那样。

列表14.有状态会话bean的部署描述符

<session>
   <description>Counter Service Bena</description>
   <ejb-name>CounterServiceEJB</ejb-name>
   <local-home>
      com.apress.prospring.ch13.ejb.CounterServiceHome</local-home>
   <local>com.apress.prospring.ch13.ejb.CounterServiceLocal</local>
   <ejb-class>com.apress.prospring.ch13.ejb.CounterServiceEJB</ejb-class>
   <session-type>Stateful</session-type>
   <transaction-type>Container</transaction-type>
   <env-entry>
      <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>applicationContext.xml</env-entry-value>
   </env-entry>
</session>

与这个部署描述符相关的是在JBoss部署描述符中的相应条目,如列表15所示。

列表15. CounterServiceEJB的JBoss 部署描述符

<session>
   <ejb-name>CounterServiceEJB</ejb-name>
   <local-jndi-name>ejb/counterService</local-jndi-name>
</session>

EJB实现总结

与使用传统的方法相比,使用Spring来实现EJB并没有太多的不同。然而,Spring支持使得实现EJB简单地将EJB的实现放入到POJO类中,这样减少了测试的障碍,有助于提高代码质量。

正如你看到的那样,实现一个无状态会话bean是相当简单的,你不必记住什么特别的要求。当实现有状态会话bean的时候,应该记住Bean所使用的BeanFactoryLocator的特别的实现是如何影响bean的挂起的能力。如果BeanFactoryLocator所返回的BeanFactory是不可串行化的,那么需要记住在ejbActivate()中调用loadBeanFactory(),在ejbPassivate()中调用unloadBeanFactory()。同样,如果所使用的BeanFactoryLocator的实现是不可串行化的,那么需要在ejbPassivate()中将它设置为null,并且,如果你没有使用缺省的实现,那么就要在ejbActivate()中重新创建它。

在某些应用程序中,如果你能够确定从BeanFactory得到的任何资源是可串行化的,那么,你可能明白这种要求。如果是这种情况,你能够简单地在ejbCreate()中保存资源,然后立即卸载BeanFactory,因为bean被激活并且被挂起,所以不需要重新载入和协作的过程。如果需要访问不可串行化的资源,那么就需要考虑使用ContextSingletonBeanFactoryLocator类来减少使用ApplicationContext而引起的频繁的重新装载的开销。

使用Spring访问EJB组件

既然已经创建了EJB组件,当然,我们就想访问他们。在前面,你看到了如何使用Spring的JDNI支持来简化JNDI资源的查找。当我们访问有状态的CounterServiceEJB时,这是非常方便的,因为能够使用JndiObjectFactoryBean将home接口表现为用Spring管理的资源。然而,对于无状态的EchoServiceEJB来说,就需要做更多的事情。我们使用LocalStatelessSessionProxyFactoryBean来创建一个EchoServiceEJB的代理,并且使用EchoService接口来直接访问它。

在这节中,使用Spring的内置特征,我们将要展示如何能够用一种比较简单的方式访问无状态和有状态会话bean。我们将要稍微提前一些在servlet例子中介绍相关的方法。然后稍后介绍完整的servler代码。

JndiObjectLocator基础组件

在对EJB访问方法进行有趣的详细介绍之前,需要说一下Spring中的JndiObjectLocator类。前面,我们向你介绍了如何在JNDI中使用JndiObjectFactoryBean来自动查找资源,以及如何通过依赖注射(DI)来完成这些过程。在这节中,我们将介绍如何使用LocalStatelessSessionProxyFactoryBean来在JNDI中查找EJB home接口,并且自动的构建一个与home接口相关联的EJB资源的代理。JndiObjectFactoryBean和LocalStatelessSessionProxyFactoryBean在JndiObjectLocator中共享一个共同的祖先。与父类类似,JndiLocatorSupport类提供了获得JNDI的通用逻辑。这对你意味着什么呢?简单地,配置LocalStatelessSessionProxyFactoryBean就像配置JndiObjectFactoryBean一样,因为大多数的属性都是放在共同的基类中的。在下一节中,你将会在实际中看到这种方法.

通过代理访问无状态会话bean

在Spring应用程序中,访问无状态会话bean最容易的办法是使用代理。Spring提供了两个无状态会话bean的代理:本地bean代理LocalStatelessSessionProxyFactoryBean,和远程bean代理SimpleRemoteStatelessSessionProxyFactoryBean。

因为EchoServiceEJB仅仅展现在本地接口中,所以在这个例子中,我们使用LocalStatelessSessionProxyFactoryBean代理,但是两个类的配置是相同的。要创建代理,需要做的所有事情就是在你的应用程序配置文件中,配置适当的FactoryBean。

列表16.配置一个无状态会话bean代理

<bean id="echoService"
      class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
   <property name="jndiName">
      <value>ejb/echoService</value>
   </property>
   <property name="resourceRef">
      <value>true</value>
   </property>
   <property name="businessInterface">
      <value>com.apress.prospring.ch13.ejb.EchoService</value>
   </property>
</bean>

这里,应该注意的第一件事事两个属性,jndiName和resourceRef,是同JndiObjectFactoryBean中使用的是相同的。正如前面所描述的那样,这是因为LocalStatelessSessionProxyFactoryBean和JndiObjectFactoryBean共享了同一个JNDI基础类。配置中指定的第三个属性businessInterface,告诉LocalStatelessSessionProxyFactoryBean代理应该实现什么接口。一般来说,这是EJB使用的业务接口。你能够使用另外的接口,或者能够限制所暴露的方法,但是,要保证你接口中的方法与EJB本地接口中的方法匹配。记住,因为LocalStatelessSessionProxyFactoryBean是一个FactoryBean,所以,Spring并不返回bean实例本身,实际上返回所FactoryBean.getObject()得到的结果,在这个例子中,就是EJB的代理。

在我们已编译的简单的servlet例子中,我们把所有与无状态EchoServiceEJB相关的代码都放到doStatelessExample()方法中。

列表17.使EJB代理起作用

private void doStatelessExample(ApplicationContext ctx, PrintWriter writer) {
   // Access the EJB proxy
   EchoService service = (EchoService) ctx.getBean("echoService");
   writer.write(service.echo("Foo"));
}

这儿,你能够看到通过代理,EchoServiceEJB是可访问的,就好像它是一个标准Spring bean一样。使用这种方法,就能够简单的将EJB组件用标准POJO组件代替,或者将POJO组件用EJB组件代替,同时不影响任何所依赖的组件

简化有状态会话bean的访问

当访问有状态会话bean时,要考虑到目前没有代理类供你使用。然而,你仍然能够通过使用在Spring中的JNDI支持,在某种程度上简化你的代码。

第一步是在你的ApplicationContext中配置bean来访问EJB home接口,如列表18所示。

列表18.使用Spring来访问EJB home

<bean id="counterServiceHome"class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiName">
      <value>ejb/counterService</value>
   </property>
   <property name="resourceRef">
      <value>true</value>
   </property>
</bean>

这里我们简单的使用JndiObjectFactoryBean来自动查找home接口。在代码中,我们避免执行手动查找,并且使用Spring来访问home接口。列表19展示了有状态会话bean的例子代码。

列表19.使用CounterServiceEJB

private void doStatefulExample(ApplicationContext ctx, PrintWriter writer,HttpSession session) {
   CounterService service = (CounterService) session.getAttribute("counterService");

   if (service == null) {

      try {
         CounterServiceHome home = (CounterServiceHome) ctx.getBean("counterServiceHome");

         service = (CounterService) home.create();

         session.setAttribute("counterService", service);
      } catch (CreateException ex) {
      ex.printStackTrace(writer);
      return;
      }
   }

writer.write("Counter: " + service.increment());
}

注意到我们能够避免执行JNDI的查找。相反地,我们依赖Spring来做这些操作。因为我们保存句柄到HttpSession中的有状态会话bean中,所以就能够在页面上看到计数器的增加。


版权所有:UML软件工程组织