Pro Spring: Spring and EJB

Learn how to use Spring with EJB applictions

With the advent of Spring, developers now have, for the first time, a truly lightweight alternative to EJB (Enterprise JavaBeans). Using Spring, you can take advantage of many of the features offered by EJB, such as declarative transaction management, object pooling, and simple ORM (object role modeling) functionality. That said, we anticipate that EJB will continue to be used for application development for the foreseeable future. Although we do not look at reasons for or against using EJB in this book, our experience with Spring has been excellent, and we recommend that you use it instead of EJB whenever you can. For a more complete discussion of the pros and cons of both Spring and EJB, read Expert One-on-One J2EE without EJB by Rod Johnson and Juergen Hoeller (Wrox, 2004). What we are going to focus on in this article is how you can continue to use Spring even though you are building your application using EJB.

EJB support in Spring

EJB support in Spring can be loosely grouped into two categories: access and implementation. The access support classes in Spring make it easier for you to access EJB resources. In this section, we look at how Spring extends the basic JNDI (Java Naming and Directory Interface) support framework to ease EJB access and utilizes AOP (aspect-oriented programming) support to provide proxy-based access to EJB resources.

The EJB implementation support in Spring provides abstract base classes that make it simpler for you to create three types of EJBs: stateless session beans, stateful session beans, and message-driven beans. The basic premise behind these base classes is not so much to ease the burden of creating an EJB, but to enable you to access Spring-managed resources easily from within your beans and, more importantly, to aid you in factoring your business logic out of the EJB implementation and into a POJO (plain old Java object) used by the EJB. Don't worry if this sounds a little unclear at this point; we discuss this in detail in the next section alongside two examples that should make this crystal clear.

We will build a simple Web application that uses two EJB services. The first, a stateless session bean, implements the EchoService business interface and provides simple echoing capabilities. The second, a stateful session bean, implements the CounterService business interface and provides stateful services for counting.

These are trivial examples, but they help to demonstrate the recommended approach to building EJBs with Spring as well as different components involved in Spring's EJB support. We do not go into great detail about the EJB spec itself, other than to discuss the various deployment descriptors that make up our sample. We do, however, peek inside the implementation of Spring's EJB support at the various components and how they affect your application. In particular, we look at how Spring locates the ApplicationContext for your EJBs and how JNDI resources are located using the JNDI infrastructure.

You may have noticed that we mentioned that Spring supports three kinds of EJB, but we are only going to be implementing two types -- stateless and stateful. The message-driven bean support classes follow a similar pattern to those for stateless and stateful session beans.

Building EJBs with Spring

Spring provides three abstract classes to serve as base classes for your EJB bean classes: AbstractStatelessSessionBean, AbstractStatefulSessionBean, and AbstractMessageDrivenBean. When building an EJB using Spring, you still have to provide all the different interfaces and the home class, but when implementing your bean class, you derive from the appropriate Spring base class. The base classes provided by Spring allow your EJBs to access a Spring ApplicationContext and thus allow them to access resources that are managed by Spring.

Before we jump into the details of building our EchoService and CounterService beans using Spring, we are going to look at how Spring goes about locating an ApplicationContext for your EJBs and the recommended approach for building an EJB when you are using Spring.

The Spring EJB class hierarchy

Spring provides a well-defined class hierarchy for the EJB support classes, as shown in Figure 1.

Figure 1. Spring EJB support classes. Click on thumbnail to view full-sized image.

As you can see, the central base class, AbstractEnterpriseBean, exposes the beanFactoryLocator property to allow subclasses access to the BeanFactoryLocator instance being used. The BeanFactoryLocator interface is discussed in more detail in the next section, along with the loadBeanFactory() and unloadBeanFactory() methods. Notice that the AbstractStatelessSessionBean class has already implemented the ejbCreate(), ejbActivate(), and ejbPassivate() methods, whereas AbstractStatefulSessionBean has not. Spring has special requirements regarding bean passivation that we discuss in more detail later, in the section entitled "Building a Stateful Session Bean."

The BeanFactoryLocator interface

One of the key features offered by the base EJB classes in Spring is the ability to access an ApplicationContext from which you can load Spring-managed resources. This functionality is not provided by the base classes themselves; rather, it is delegated to an implementation of the BeanFactoryLocator interface, like the one shown in Listing 1.

Listing 1. The BeanFactoryLocator Interface

 

public interface BeanFactoryLocator {

BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException;

}

A BeanFactoryLocator is used in circumstances when Spring has no control over the creation of a resource and thus cannot automatically configure it. In these circumstances, a BeanFactoryLocator allows a resource to locate the BeanFactory itself in an externally configurable way. Of course, in such cases, the resource can simply mandate where the BeanFactory configuration must be, but that means that you, as the application developer, have no control over the application. By using BeanFactoryLocator, you can fully control how your EJBs locate the BeanFactory or ApplicationContext they use for configuration.

Notice that the BeanFactoryLocator doesn't return a BeanFactory instance directly; instead it returns a BeanFactoryReference. A BeanFactoryReference is a lightweight wrapper around a BeanFactory or ApplicationContext that allows the resource using the BeanFactory to release its reference to the BeanFactory gracefully. The actual implementation of this interface is specific to both the BeanFactoryLocator implementation and the BeanFactory or ApplicationContext interface. We investigate this functionality a little more in Listing 13, when we look at stateful session beans that use their ability to release a BeanFactoryReference to enable bean passivation.

By default, all of the base EJB classes use the ContextJndiBeanFactoryLocator implementation of BeanFactoryLocator. Essentially, this class looks in a given JNDI location for a comma-separated list of configuration filenames and creates an instance of ClassPathXmlApplicationContext using these configuration files. You can provide your own implementation of BeanFactoryLocator by setting the beanFactoryLocator property that is exposed by all three base EJB classes via the AbstractEnterpriseBean class. However, if you do so, be aware that each instance of your bean has its own instance of ContextJndiBeanFactoryLocator, and likewise, each instance of ContextJndiBeanFactoryLocator has its own instance of ClassPathXmlApplicationContext.

Although all the ApplicationContext instances created for your EJB instances are identically configured, the beans are not identical. Consider a Spring configuration that defines the echoService bean the EchoServiceEJB will use. If your application server creates 100 instances of your EJB, then 100 instances of ContextJndiBeanFactoryLocator are created, along with 100 instances of ClassPathXmlApplicationContext and 100 instances of the echoService bean.

If this behavior is undesirable for your application, then Spring provides the SingletonBeanFactoryLocator and ContextSingletonBeanFactoryLocator classes that load singleton instances of BeanFactory and ApplicationContext, respectively. For more information, see the Javadoc for these classes.

The Spring approach to EJB

One of the biggest drawbacks of EJB is that it is very difficult to test EJB components separately from the EJB container, which makes unit testing EJB-implemented business logic a feat only attempted by the particularly masochistic. However, a workaround to this problem has been around in the Java world for a long while; it involves implementing your business logic in a POJO that implements the same business interface as your EJB bean class; you can then have the bean class delegate to the POJO. Using Spring makes this implementation much simpler and much more flexible because you don't have to embed any logic inside the EJB as to how the POJO implementation is located and created. Figure 2 shows how we employ this approach when building the EchoService EJB.

Figure 2. Using a POJO implementation for an EJB

Here, you can see that, as expected, the bean class, EchoServiceEJB, implements the EchoService interface, but also notice that EchoServiceEJB has a dependency on the EchoService interface and a private field of type EchoService.

Building a stateless session bean

A stateless session bean is the easiest EJB to build with Spring; this is because it requires no special handling whatsoever and all the ejbXXX() methods are implemented by the AbstractStatelessSessionBean class.

We start by creating the service interface, as shown in Listing 2.

Listing 2. The EchoService Interface

 

package com.apress.prospring.ch13.ejb;

public interface EchoService {

public String echo(String message); }

Notice that the service interface is not EJB-specific, and indeed, it is not required when implementing the EJB. The traditional approach to EJB development is to define business methods in the EJB-specific local and remote interfaces. In this approach, the business methods are defined in a standard Java interface, and the local and remote bean interfaces extend this standard interface. This service interface provides a standard interface not only for the local and remote interfaces to extend, but it also provides a common interface that both the EJB bean class and the POJO implementation class can implement. Although we recommend that your EJB bean class does not implement either the local or remote interface, there is no problem with the EJB bean implementing the service interface. By having all the components that make up the EJB share a common interface, it is easier to ensure that your local interface defines the methods you intend it to, that your bean class implements the methods expected by the local interface, and that the POJO implementation implements the methods required by the EJB bean class.

The next step is to create the bean interface. In this example, we do not use the EJB in a remote container, so we stick to a local interface, as shown in Listing 3.

Listing 3. The local interface for the EchoService EJB

 

package com.apress.prospring.ch13.ejb;

import javax.ejb.EJBLocalObject;

public interface EchoServiceLocal extends EchoService, EJBLocalObject {

}

Notice that, as we discussed earlier, no business methods are defined in this interface. Instead, the EchoServiceLocal interface extends the service interface, EchoService.

Next, we need to create the EJB home interface. For this example, we are not going to be invoking the EJB remotely, so we stick to a simple local home interface as shown in Listing 4.

Listing 4. Local home interface for EchoService EJB

 

package com.apress.prospring.ch13.ejb;

import javax.ejb.CreateException; import javax.ejb.EJBLocalHome;

public interface EchoServiceHome extends EJBLocalHome {

public EchoServiceLocal create() throws CreateException; }

That takes care of most of the boilerplate code required by the EJB specification.

When you are building an EJB using a traditional architecture, the next step is to create the bean class. However, we are going to factor the implementation of the EchoService into a POJO and have the EJB bean class delegate to this POJO implementation. Listing 5 shows the POJO implementation of the EchoService interface.

Listing 5. POJO Implementation of EchoService

 

package com.apress.prospring.ch13.ejb;

public class EchoServiceImpl implements EchoService {

public String echo(String message) { return message;

} }

Here, all of the implementations of EchoService are contained in a POJO that you can easily test outside of the EJB container (Not that this implementation needs much testing!).

The final step in the implementation of the EchoService EJB is to create the bean class itself. This is where Spring comes into the equation. With the actual implementation of the EchoService interface contained in EchoServiceImpl, we can simply choose to create an instance of this instance EchoServiceEJB and be done with it.

However, what happens if we want to change the implementation? We need to recompile and redeploy the EJB. By using Spring, we can load the implementation class and any dependencies, from the ApplicationContext. This means that you can take full advantage of all of Spring's features for the actual implementation class, including DI, AOP, and external configuration support. Listing 6 shows the implementation of the EchoService bean class.

Listing 6. The EchoServiceEJB class

 

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

There are a few interesting points to note in this code. First, you should notice that EchoServiceEJB extends the Spring base class AbstractStatelessSessionBean and implements the EchoService interface. Second, all the methods on the EchoService interface are delegated to the wrapped implementation of EchoService. Third, this bean has no ejbXXX() methods—these are all implemented in AbstractStatelessSessionBean. And finally, notice the onEjbCreate() method. This is a hook method AbstractStatelessSessionBean provides, and it is called during ejbCreate(). This method is perfect for obtaining any Spring-managed resources the EJB bean needs, in particular, the actual implementation of the service interface. As you can see, we retrieved the echoService bean from Spring and stored it in the service field. This bean is the implementation of EchoService to which the EchoServiceEJB delegates.

Remember from Figure 1 that the getBeanFactory() method is declared on the AbstractEnterpriseBean class that forms the base of all Spring EJB implementation support classes. By default, the AbstractEnterpriseBean class uses an instance of ContextJndiBeanFactoryLocator to locate and load an ApplicationContext to return from getBeanFactory(). ContextJndiBeanFactoryLocator works by looking in a particular JNDI location for a list of file names from which to load the ApplicationContext configuration. We can specify this list in the deployment descriptor for the EJB as shown in Listing 7.

Listing 7. Configuring ContextJndiBeanFactoryLocator in deployment descriptor

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

The important part in this deployment descriptor is the <env-entry> tag that sets the value of the ejb/BeanFactoryPath JNDI location to applicationContext.xml. The JNDI path ejb/BeanFactoryPath is the default location where ContextJndiBeanFactoryLocator looks for the configuration file path. The configuration in Listing 7 is essentially telling the instance of ContextJndiBeanFactoryLocator associated with the EchoServiceEJB to create an ApplicationContext configured using the details in the applicationContext.xml file.

An important point to note is that this configuration file is specific to the EJB and is separate from any applicationContext.xml file you may have for the main application. When we package up the application, we place the applicationContext.xml file specific to the EJBs in the EJB JAR, and then we have the applicationContext.xml file for the Web application in the war file. To finish up with the deployment of the EchoServiceEJB, we also need the JBoss-specific deployment descriptor for the bean. This is shown in Listing 8.

Listing 8. JBoss deployment descriptor

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

The important part in this deployment descriptor is the <local-jndi-name> tag, which tells JBoss the JNDI name to which to bind the EJB home. In this case, we are able to locate an instance of EchoServiceHome under ejb/echoService.

That's all for the stateless session bean. As you can see, the implementation steps aren't vastly different from the traditional approach to EJB implementation. However, with the approach taken here, you gain the ability to test your business logic easily and without any dependency on the EJB container. By using Spring, we have made the POJO-based implementation approach much more flexible, and we are easily able to avoid coupling the EJB bean to a particular POJO implementation.

Building a stateful session bean

Building a stateful session bean is slightly more complex than building a stateless session bean because you now have to consider what happens to the ApplicationContext when the bean is passivated. Recall from Figure 1 that AbstractStatefulSessionBean does not implement ejbCreate(), ejbActivate(), and ejbPassivate(). The reason for this is that none of the default BeanFactory and ApplicationContext implementations Spring provides is serializable, and as a result, none can be passivated along with your stateful session bean.

To get around this, you have two options. The simplest option is to implement ejbActivate() and ejbPassivate() to load and unload the ApplicationContext as appropriate, which allows the bean to be passivated without storing the ApplicationContext and reconstructing the ApplicationContext when the bean is activated again. The second option is to provide your own implementation of BeanFactoryLocator that creates a BeanFactory or ApplicationContext that is serializable. When you use the first approach, be aware that when you use the ContextJndiBeanFactoryLocator, bean activation results in the ApplicationContext being loaded from scratch. If this is unacceptable overhead for your application, you can swap ContextJndiBeanFactoryLocator for ContextSingletonBeanFactoryLocator, as discussed earlier.

As before, to start building the bean, we start with the service interface, shown here in Listing 9.

Listing 9. The CounterService interface

 

package com.apress.prospring.ch13.ejb;

public interface CounterService {

public int increment();

public int decrement(); }

Again, we created a basic service interface that will be implemented by the local interface, the bean class, and the implementation class. The next step is to create the local interface, shown in Listing 10.

Listing 10. Local interface for CounterService

 

package com.apress.prospring.ch13.ejb;

import javax.ejb.EJBLocalObject;

public interface CounterServiceLocal extends CounterService, EJBLocalObject {

}

Again, the local interface itself contains no method definitions, and all the business methods are inherited from the CounterService interface. Next up, we need to create the home interface, as shown in Listing 11.

Listing 11. The local home interface for CounterService

 

package com.apress.prospring.ch13.ejb;

import javax.ejb.CreateException; import javax.ejb.EJBLocalHome;

public interface CounterServiceHome extends EJBLocalHome {

public CounterServiceLocal create() throws CreateException; }

Again, there is nothing special about this implementation; it is just the standard EJB approach.

The fourth component required for the CounterService EJB is the POJO implementation. This implementation is stateful and is marked as Serializable, so it can be passivated along with the CounterServiceEJB. Listing 12 shows the CounterServiceImpl class.

Listing 12. Basic CounterService implementation

 

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; } }

Thus far, the implementation of the stateful session bean has been similar to the implementation of the stateless session bean. However, there are notable differences when implementing the bean class. Listing 13 shows the bean class for the CounterServiceEJB.

Listing 13. The CounterServiceEJB class

 

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

The first part of the bean class implementation is similar to that of the EchoServiceEJB that we created earlier. However, the noticeable differences here are in the ejbCreate(), ejbActivate(), and ejbPassivate() methods. In ejbCreate(), we invoke the load() method, which in turn invokes loadBeanFactory() on AbtstractEnterpriseBean. This causes the BeanFactoryLocator implementation to load the BeanFactory and makes it available via a call to getBeanFactory(). Finally the ejbCreate() method uses the BeanFactory to access the counterService bean and stores it in the service field.

The bean is now configured for use and will continue to function happily until the container chooses to passivate it. When this happens, the ejbPassivate() method is invoked and, in turn, the unload() method. The first step unload() takes is to invoke unloadBeanFactory(), which clears out the ApplicationContext loaded by the ContextJndiBeanFactoryLocator and sets the reference to null. As we mentioned earlier, the reason for this is that ClassPathXmlApplicationContext (the ApplicationContext implementation used by ContextJndiBeanFactoryLocator) is not serializable, and, as a result, it cannot be passivated along with the bean. Finally, unload() removes all references to the BeanFactoryLocator implementation because, like ClassPathXmlApplicationContext, ContextJndiBeanFactoryLocator is not serializable.

When the container is ready to reactivate a bean after passivation, it invokes ejbActivate() on the bean to let the bean know it can reestablish any state that could not be passivated. In this case, we simply invoke load() again to reload the ApplicationContext. Note that if you are using a custom BeanFactoryLocator implementation, then you need to reinstantiate it in ejbActivate() as well. Once the ApplicationContext is reloaded, we reload the counterService bean from the ApplicationContext.

You may well be wondering why we bother to reload the ApplicationContext in ejbCreate(). Given that the CounterServiceImpl class is serializable, it will be passivated along with the bean, so all we really need to do is close the BeanFactory once we obtain the counterService bean. However, remember that you can configure any CounterService implementation in the ApplicationContext, including one that is not serializable. If you can guarantee that all implementations are serializable, then you can opt for this approach, perhaps supplementing it with a check in ejbCreate() to ensure that the implementation is actually serializable. Where you cannot guarantee that the implementation is serializable, you have to assume that it is not; therefore, you have to unload the BeanFactory at passivation and reload it on activation.

As with the stateless session bean, we need to define the location of the configuration for the ApplicationContext in the EJB deployment descriptor; this is shown in Listing 14.

Listing 14. Deployment descriptor for stateful session 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>

Accompanying this is the corresponding entry in the JBoss-specific deployment descriptor, shown in Listing 15.

Listing 15. JBoss deployment descriptor for CounterServiceEJB

 <session>
   <ejb-name>CounterServiceEJB</ejb-name>
   <local-jndi-name>ejb/counterService</local-jndi-name>
</session>
1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies