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.
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.