20.3 Using Spring's EJB implementation support classes

20.3.1 EJB 2.x base classes

Spring provides convenience classes to help you implement EJBs. These are designed to encourage the good practice of putting business logic behind EJBs in POJOs, leaving EJBs responsible for transaction demarcation and (optionally) remoting.

To implement a Stateless or Stateful session bean, or a Message Driven bean, you need only derive your implementation class from AbstractStatelessSessionBean, AbstractStatefulSessionBean, and AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean, respectively.

Consider an example Stateless Session bean which actually delegates the implementation to a plain java service object. We have the business interface:

public interface MyComponent {
    public void myMethod(...);
    ...
}

We also have the plain Java implementation object:

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

And finally the Stateless Session Bean itself:

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(...);
    }
    ...
}

The Spring EJB support base classes will by default create and load a Spring IoC container as part of their lifecycle, which is then available to the EJB (for example, as used in the code above to obtain the POJO service object). The loading is done via a strategy object which is a subclass of BeanFactoryLocator. The actual implementation of BeanFactoryLocator used by default is ContextJndiBeanFactoryLocator, which creates the ApplicationContext from a resource locations specified as a JNDI environment variable (in the case of the EJB classes, at java:comp/env/ejb/BeanFactoryPath). If there is a need to change the BeanFactory/ApplicationContext loading strategy, the default BeanFactoryLocator implementation used may be overridden by calling the setBeanFactoryLocator() method, either in setSessionContext(), or in the actual constructor of the EJB. Please see the Javadocs for more details.

As described in the Javadocs, Stateful Session beans expecting to be passivated and reactivated as part of their lifecycle, and which use a non-serializable container instance (which is the normal case) will have to manually call unloadBeanFactory() and loadBeanFactory from ejbPassivate and ejbActivate, respectively, to unload and reload the BeanFactory on passivation and activation, since it can not be saved by the EJB container.

The default behavior of the ContextJndiBeanFactoryLocator classes which is to load an ApplicationContext for the use of the EJB is adequate for some situations. However, it is problematic when the ApplicationContext is loading a number of beans, or the initialization of those beans is time consuming or memory intensive (such as a Hibernate SessionFactory initialization, for example), since every EJB will have their own copy. In this case, the user may want to override the default ContextJndiBeanFactoryLocator usage and use another BeanFactoryLocator variant, such as the ContextSingletonBeanFactoryLocator which can load and use a shared container to be used by multiple EJBs or other clients. Doing this is relatively simple, by adding code similar to this to the 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);
   }

You would then need to create a bean definition file named beanRefContext.xml. This file defines all bean factories (usually in the form of application contexts) that may be used in the EJB. In many cases, this file will only contain a single bean definition such as this (where businessApplicationContext.xml contains the bean definitions for all business service POJOs):

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

In the above example, the ServicesConstants.PRIMARY_CONTEXT_ID constant would be defined as follows:

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

Please see the respective Javadocs for the BeanFactoryLocator and ContextSingletonBeanFactoryLocator classes for more information on their usage.

20.3.2 EJB 3 injection interceptor

For EJB 3 Session Beans and Message-Driven Beans, Spring provides a convenient interceptor that resolves Spring 2.5's @Autowired annotation in the EJB component class: org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor. This interceptor can be applied through an @Interceptors annotation in the EJB component class, or through an interceptor-binding XML element in the EJB deployment descriptor.

@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 by default obtains target beans from a ContextSingletonBeanFactoryLocator, with the context defined in a bean definition file named beanRefContext.xml. By default, a single context definition is expected, which is obtained by type rather than by name. However, if you need to choose between multiple context definitions, a specific locator key is required. The locator key (i.e. the name of the context definition in beanRefContext.xml) can be explicitly specified either through overriding the getBeanFactoryLocatorKey method in a custom SpringBeanAutowiringInterceptor subclass.

Alternatively, consider overriding SpringBeanAutowiringInterceptor's getBeanFactory method, e.g. obtaining a shared ApplicationContext from a custom holder class.