Spring也提供了一些辅助类来为EJB组件的实现提供便利。它们是为了倡导一些好的实践经验,比如把业务逻辑放在在EJB层之后的POJO中实现,只把事务划分和远程调用这些职责留给EJB。
要实现一个无状态或有状态的Session Bean,或消息驱动Bean,你只需要从AbstractStatelessSessionBean
、AbstractStatefulSessionBean
和AbstractMessageDrivenBean
/AbstractJmsMessageDrivenBean
分别继承你的实现类。
考虑这个无状态Session bean的例子:实际上我们把无状态Session Bean的实现委托给一个普通的Java服务对象。业务接口的定义如下:
public interface MyComponent { public void myMethod(...); ... }
这是简单Java对象的实现:
public class MyComponentImpl implements MyComponent { public String myMethod(...) { ... } ... }
最后是无状态Session Bean自身:
public class MyFacadeEJB extends AbstractStatelessSessionBean implements MyFacadeLocal { private MyComponent myComp; /** * Obtain our POJO service object from the BeanFactory/ApplicationContext * @see org.springframework.ejb.support.AbstractStatelessSessionBean#onEjbCreate() */ protected void onEjbCreate() throws CreateException { myComp = (MyComponent) getBeanFactory().getBean( ServicesConstants.CONTEXT_MYCOMP_ID); } // for business method, delegate to POJO service impl. public String myFacadeMethod(...) { return myComp.myMethod(...); } ... }
缺省情况下,Spring EJB支持类的基类在其生命周期中将创建并加载一个Spring IoC容器供EJB使用(比如像前面获得POJO服务对象的代码)。加载的工作是通过一个策略对象完成的,它是BeanFactoryLocator
的子类。
默认情况下,实际使用的BeanFactoryLocator
的实现类是ContextJndiBeanFactoryLocator
,它根据一个被指定为JNDI环境变量的资源位置来创建一个ApplicationContext对象(对于EJB类,路径是
java:comp/env/ejb/BeanFactoryPath
)。如果需要改变BeanFactory或ApplicationContext的载入策略,我们可以在
setSessionContext()
方法调用或在具体EJB子类的构造函数中调用setBeanFactoryLocator()
方法来覆盖默认使用的
BeanFactoryLocator
实现类。具体细节请参考JavaDoc。
如JavaDoc中所述,有状态Session Bean在其生命周期中将会被钝化并重新激活,由于(一般情况下)使用了一个不可串行化的容器实例,不可以被EJB容器保存,
所以还需要手动在ejbPassivate
和ejbActivate
这两个方法中分别调用unloadBeanFactory()
和loadBeanFactory
,
才能在钝化或激活的时候卸载或载入。
有些情况下,要载入ApplicationContext以使用EJB组件,ContextJndiBeanFactoryLocator
的默认实现基本上足够了,
不过,当ApplicationContext
需要载入多个bean,或这些bean初始化所需的时间或内存
很多的时候(例如Hibernate的SessionFactory
的初始化),就有可能出问题,因为
每个EJB组件都有自己的副本。这种情况下,用户会想重载ContextJndiBeanFactoryLocator
的默认实现,并使用其它
BeanFactoryLocator
的变体,例如ContextSingletonBeanFactoryLocator
,他们可以载入并在多个EJB或者其客户端间共享一个容器。这样做相当简单,只需要给EJB添加类似于如下的代码:
/** * Override default BeanFactoryLocator implementation * @see javax.ejb.SessionBean#setSessionContext(javax.ejb.SessionContext) */ public void setSessionContext(SessionContext sessionContext) { super.setSessionContext(sessionContext); setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance()); setBeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID); }
然后需要创建一个名为beanRefContext.xml
的bean定义文件。这个文件定义了EJB中所有可能用到的bean工厂(通常以应用上下文的形式)。许多情况下,这个文件只包括一个bean的定义,如下所示(文件businessApplicationContext.xml
包括了所有业务服务POJO的bean定义):
<beans> <bean id="businessBeanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext"> <constructor-arg value="businessApplicationContext.xml" /> </bean> </beans>
上例中,常量ServicesConstants.PRIMARY_CONTEXT_ID
定义如下:
public static final String ServicesConstants.PRIMARY_CONTEXT_ID = "businessBeanFactory";
BeanFactoryLocator
和类ContextSingletonBeanFactoryLocator
的更多使用信息请分别查看他们各自的Javadoc文档。
对EJB3 Session bean和Message-Driven Bean来说, Spring在EJB组件类
org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor
中提供了实用的拦截器来解析Spring2.5的注解@Autowired
。
这个拦截器的使用有两种方式,可以在EJB组件类里使用@Interceptors
注解,也可以在EJB部署描述文件中使用XML元素interceptor-binding
。
@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(...); } ... }
SpringBeanAutowiringInterceptor
默认情况下是从ContextSingletonBeanFactoryLocator
获得目标bean的,后者定义在beanRefContext.xml
文件中。通常情况下,最好使用单独的上下文定义,并且根据类型而不是名称来获得。然而,如果你需要在多个上下文定义中切换,那么就需要一个特定的定位键。这个定位键(例如定义在beanRefContext.xml
中的上下文名称)可以通过以下两种方式来明确的指定。一种方式是在定制的SpringBeanAutowiringInterceptor
子类中重写getBeanFactoryLocatorKey
方法。
另一种方式是重写SpringBeanAutowiringInterceptor
的
getBeanFactory
方法,例如从定制支持类中获得一个共享的ApplicationContext
。