Implementing EJBs with Spring


Spring provides a number of abstract base classes you may derive from to make it easier to implement Stateless Session Beans, Stateful Session Beans, and Message Driven Beans. Aside from reducing the boilerplate code you have to write, these classes encourage and simplify the generally desirable practice of actually implementing business logic in POJOs to which the EJBs delegate to, as opposed to in the EJBs themselves. These base classes are AbstractStatelessSessionBean, AbstractStatefulSessionBean, and AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean.

Stateless Session Beans

Let's look at the absolute simplest Stateless Session Bean possible to be implemented by deriving from AbstractStatelessSessionBean:

public class SimpleEJB extends AbstractStatelessSessionBean {  }

This EJB is, of course, useless, but demonstrates that the classes in the abstract base hierarchy provided by Spring implement all required Session Bean EJB lifecycle methods. If the EJB implementation class needs to hook into any of the lifecycle methods, it generally does so by overriding template methods provided by the base class hierarchy. It's necessary to override template methods and not the actual life- cycle methods because Spring classes in the base class hierarchy implement the lifecycle callback methods themselves to provide configuration management. The actual lifecycle methods would have been made final were it not for EJB restrictions.

Let's look at the superclass template methods and other methods that may be called or have to be implemented by your EJB implementation classes. From AbstractEnterpriseBean:

 /**  * Subclasses must implement this method to do any cleanup  * they would otherwise have done in an ejbRemove() method.  * The BeanFactory will be unloaded afterwards.  * <p>This implementation is empty, to be overridden in subclasses.  * The same restrictions apply to the work of this method as to  * an ejbRemove() method.  */ protected void onEjbRemove() {   // empty } 

From AbstractSessionBean:

 /**  * Sets the session context.  * <p><b>If overriding this method, be sure to invoke this form of it  * first.</b>  * @param sessionContext SessionContext context for session  */ public void setSessionContext(SessionContext sessionContext) {     this.sessionContext = sessionContext; } /**  * Convenience method for subclasses.  * Return the EJB context saved on initialization.  * @return the SessionContext saved on initialization by this class’s  * implementation of the setSessionContext() method.  */ protected final SessionContext getSessionContext() {   return sessionContext; } 

From AbstractStatelessSessionBean:

 /**  * Subclasses must implement this method to do any initialization  * they would otherwise have done in an ejbCreate() method. In contrast  * to ejbCreate, the BeanFactory will have been loaded here.  * <p>The same restrictions apply to the work of this method as  * to an ejbCreate() method.  * @throws CreateException  */ protected abstract void onEjbCreate() throws CreateException; 

Now let's examine a real EJB:

public class SimpleEJB extends AbstractStatelessSessionBean         implements SimpleService {       // --- statics   public static final String POJO_SERVICE_ID = "simpleService";       protected SimpleService simpleService;       protected void onEjbCreate() throws CreateException {     simpleService = (SimpleService) getBeanFactory().getBean(POJO_SERVICE_ID);   }       public String echo(String input) {     return simpleService.echo(input);   }   public String echo2(String input) {     return simpleService.echo2(input);   }   public String echo3(String input) {     return simpleService.echo3(input);   } }

This version implements the SimpleService business interface. However, rather than actually implementing business method functionality itself, it delegates method calls to a POJO implementation of that interface. While the EJB could implement business functionality itself, delegating to a POJO is an option that should be favored because it makes testing much easier. Generally, EJBs are not easily tested inside the application server, while if the real functionality is in a POJO, it can simply be tested with an out-of- server unit or integration test. Additionally, it may make migrating away from EJBs much easier because the client can be hooked up directly to the POJO, instead of going through the intermediary EJB.

Important 

Decoupling business logic from any environmental API — EJB or other — is usually desirable to promote reusability and achieve a degree of future proofing. The fact that EJB 3.0 will mark a move away from the traditional EJB API — at least in terms of the recommended programming model — underlines this point where EJB is concerned.

The POJO service implementation is obtained from an application context loaded by the base classes; application code simply calls getBeanFactory() to obtain the application context. How and from where the application context is loaded, and whether in fact it is a bean factory or application context, is pluggable, but there is a default behavior, which the class in the preceding code sample relies on. In the default implementation, the value at the bean-specific JNDI location

java:comp/env/ejb/BeanFactoryPath

is treated as the classpath location of an XML application context definition to load. Let's look at our EJB definition from the standard ejb-jar.xml:

     <session>       <description>Chap11Sample3Simple Session Bean - Stateless Session Bean for Chapter 11 Sample 30 implementing SimpleService</description>       <display-name>Chap11Sample3Simple</display-name>       <ejb-name>Chap11Sample3Simple</ejb-name>       <local-home>ch11.sample3.ejb.SimpleHomeLocal</local-home>       <local>ch11.sample3.ejb.SimpleLocal</local>       <ejb-class>ch11.sample3.ejb.SimpleEJB</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>ch11/sample3/ejb/beans.xml</env-entry-value>       </env-entry>           </session> 

In this definition, there is an EJB environment entry defined for the name ejb/BeanFactoryPath (which the bean will access as java:comp/env/ejb/BeanFactoryPath), with a value of ch11/sample3/ejb/beans.xml, the application context definition classpath location. Here's the actual definition:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"     "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>   <bean         /> </beans> 

In this case, the only bean defined is the POJO implementation of SimpleService that the EJB is delegating to.

So in the default setup, each EJB may specify its own unique application context definition location, or use the same definition location as another EJB does. However, regardless of where the definition comes from, each actual EJB instance — remember that EJBs are pooled by the EJB container — will still have its own unique application context instance created when the EJB itself is created. Some readers may be thinking to themselves that this is perfectly workable when the context holds small amounts of configuration data or beans, but has potential issues when some beans that are expensive (in terms of time to initialize or in terms of memory usage) need to be defined in the context and used. In fact, as we mentioned, the bean factory or application context loading mechanism is pluggable, and there is a way to switch to alternate setups, such as one where a common, singleton application context is shared by all the EJBs. We'll investigate this after we finish looking at the other EJB types.

Stateful Session Beans

Let's look at a Stateful Session Bean implemented by deriving from Spring's AbstractStatefulSessionBean abstract base class:

 public class StatefulEJB extends AbstractStatefulSessionBean implements         StatefulService { 

The EJB implements a StatefulService business interface, with three methods: clear(), addItem(), and getItems():

 Collection items = new ArrayList();      public void clear() {   items.clear(); } public void addItem(String item) {   items.add(item); } public Collection getItems() {   return items; } 

Finally, as per the Stateless Session Bean specification, we need to implement ejbActivate() and ejbPassivate(), and define at least one create method:

   // see javax.ejb.SessionBean#ejbActivate()   public void ejbActivate() {     super.loadBeanFactory();   }        // see javax.ejb.SessionBean#ejbPassivate()   public void ejbPassivate() {     super.unloadBeanFactory();   }        public void ejbCreate() {     super.loadBeanFactory();   } } 

At a minimum, the create method(s) and ejbActivate() must call super.loadBeanFactory(), and ejbPassivate() must call super.unloadBeanFactory(), to help the Spring base class take care of loading the default application context or bean factory that is loaded for each bean, as per the description in the previous section. Here you should also take care of any of your own state that needs to be handled properly as per normal EJB create method, activation, and passivation semantics.

The AbstractEnterpriseBean.onEjbRemove(), and AbstractSessionBean setSessionContext() and getSessionContext() methods are still available to the Stateful Session Bean, and it may also call getBeanFactory() to get the application context or bean factory loaded by the Spring superclasses.

Message Driven Beans

The final EJB type that Spring provides an abstract base class for is the Message Driven Bean. Let's look at an example:

 public class DispatcherEJB extends AbstractMessageDrivenBean {        // see org.springframework.ejb.support.AbstractMessageDrivenBean#onEjbCreate()   protected void onEjbCreate() {   }        public void onMessage(Message msg) {     // do some work here   } } 

In addition to the previously mentioned AbstractEnterpriseBean.onEjbRemove() template method, which may be overridden if needed, AbstractMessageDriveBean provides a getMessageDrivenContext() method:

 protected final MessageDrivenContext getMessageDrivenContext(); 

to obtain the message driven context, which it has saved in a variable for this purpose. It also forces you to define a create method called onEjbCreate():

 /**  * Subclasses must implement this method to do any initialization  * they would otherwise have done in an ejbCreate() method. In contrast  * to ejbCreate, the BeanFactory will have been loaded here.  * <p>The same restrictions apply to the work of this method as  * to an ejbCreate() method.  */ protected abstract void onEjbCreate(); 

As with the other bean types, Spring itself implements and uses the real ejbCreate() method, and then calls this template method meant for application use. As with other bean types, your code may also call getBeanFactory() to get the application context or bean factory loaded by the Spring superclasses.

Spring also provides an AbstractJmsMessageDrivenBean abstract superclass. This is simply an AbstractMessageDrivenBean subclass whose purpose is simply to force subclasses to implement the javax.jms.MessageListener interface:

 public abstract class AbstractJmsMessageDrivenBean      extends AbstractMessageDrivenBean implements MessageListener {   ... } 

A Digression into XDoclet

XDoclet (http://xdoclet.sourceforge.net) is a code generation tool that is used successfully by many Java developers to automatically generate boilerplate style code dynamically at build time, instead of having to create it by hand. Many people creating EJBs prefer to use XDoclet to generate artifacts such as the EJB deployment descriptors, the local and/or remote interfaces, and so on, by using metadata from JavaDoc tags inside your EJB source file.

If you are not interested in using XDoclet for creating EJB artifacts, you may safely skip the rest of this section. We do generally recommend the use of XDoclet as a timesaver when creating EJBs, although the following text assumes you are already familiar with it. Please see the XDoclet documentation and samples for more information on XDoclet.

Unfortunately XDoclet is a bit dense (as of version 1.2.1) when it comes to working with EJB implementation files, which extend from a base class. If you have such an EJB implementation extending from Spring's AbstractStatelessSessionBean or AbstractStatefulSessionBean, XDoclet will not realize that the Spring classes already implement the javax.ejb.SessionBean interface, and will complain that your code does not implement this interface. Because of this, when using the Spring Session Bean base classes, you must add a redundant implements SessionBean declaration even in your own class:

public class SimpleEJB extends AbstractStatelessSessionBean           implements SimpleService, SessionBean { ...

Similarly, when using XDoclet with an EJB implementation class that derives from Spring's AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean, you must add a redundant implements MessageDrivenBean clause.

Finally, XDoclet is not smart enough to figure out where the Spring superclasses implement ejbCreate() themselves (in the case of Stateless Session Beans and Message Driven Beans), and will itself add its own ejbCreate() method in the final generated class it puts out. This is completely undesirable and disrupts the normal automatic loading of an application context or bean factory that the Spring version of this method kicks off. This issue is easy to get around; just add in your actual implementation class an instance of ejbCreate(), which delegates to the Spring one. XDoclet will see this version, and not generate its own.

 /*  * This method needed just to make XDoclet happy. As it doesn’t  * coming from the Spring superclass, it will create an empty ejbCreate() in the  * subclass it generates from this one, overriding the vital superclass version.  * @ejb.create-method  */  public void ejbCreate() throws CreateException {    super.ejbCreate();  } 



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net