Pro Spring: Spring and EJB
Learn how to use Spring with EJB applictions
Summary
In traditional J2EE applications, Enterprise JavaBeans (EJB) often forms the cornerstone of an application's architecture. Although Spring provides simpler versions of many of the services provided by EJB, such as declarative transaction management and object persistence, many applications will continue to be built using EJBs for some time. Thankfully, you can still utilize Spring in an EJB-based solution, and in this article, an excerpt from Pro Spring (Apress, January 2005), authors Rob Harrop and Jan Machacek look at how Spring can simplify the creation of EJBs and provide a straightforward, DI-friendly way to access EJB resources. (4,600 words; February 14, 2005)
By Rob Harrop and Jan Machacek
ith 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>
EJB implementation summary
Implementing EJBs with Spring is not drastically different from implementing EJBs using traditional approaches. However, Spring support makes it simple to factor out the implementation of your EJBs into a POJO class, thus reducing the barriers to testing and helping you deliver quality software.
As you saw, implementing a stateless session bean is pretty painless—you have no special requirements to bear in mind. When implementing stateful session beans, be aware of how the particular implementation of BeanFactoryLocator
your bean is using affects the bean's ability to passivate successfully. If the BeanFactory
returned by the BeanFactoryLocator
is not serializable, then you need to remember to call loadBeanFactory()
from ejbActivate()
and unloadBeanFactory()
from ejbPassivate()
. Likewise, if the implementation of BeanFactoryLocator
being used is not serializable, you need to set it to null
in ejbPassivate()
and, if you are not using the default implementation, re-create it in ejbActivate()
.
In some applications, you can get around this requirement if you can ensure that any resources you obtain from the BeanFactory
are serializable. If this is the case, you can simply store the resources in ejbCreate()
and then unload the BeanFactory
immediately, with no need to reload and unload because the bean is activated and passivated. If you need to access resources that aren't serializable, then consider using the ContextSingletonBeanFactoryLocator
class to reduce the overhead of the ApplicationContext
constantly being reloaded.
That concludes the implementation of EJBs using Spring. Next, you see how to access these resources easily using Spring's EJB access support classes.
Accessing EJBs with Spring
Now that we have created our EJBs, we want, of course, to access them. Earlier, you saw how to use Spring's JNDI support to simplify the lookup of JNDI resources. This comes in handy when we are trying to access the stateful CounterServiceEJB
, because we can expose the home interface as a Spring-managed resource using JndiObjectFactoryBean
. However, for the stateless EchoServiceEJB
, we can go a step further. We use the LocalStatelessSessionProxyFactoryBean
to create a proxy to the EchoServiceEJB
and access it directly using the EchoService
interface.
In this section, we are going to demonstrate how you can access both stateless and stateful session beans in a simpler manner using Spring's built-in features. We are going to jump ahead of ourselves slightly and show just the relevant methods from the example servlet. We show the full servlet code later.
The JndiObjectLocator infrastructure component
Before we jump into the juicy details of EJB access, we want to talk about the JndiObjectLocator
class in Spring. Earlier, we showed you how you can use JndiObjectFactoryBean
to look up a resource in JNDI automatically and make it available via DI. In this section, we show you how to use LocalStatelessSessionProxyFactoryBean
to look up an EJB home interface in JNDI and automatically build a proxy to the EJB resource associated with that home interface. Both JndiObjectFactoryBean
and LocalStatelessSessionProxyFactoryBean
share a common ancestor in JndiObjectLocator
. This class, along with its parent, JndiLocatorSupport
, provides the common logic for obtaining JNDI objects. What does this mean for you? Simply that configuring a LocalStatelessSessionProxyFactoryBean
is just like configuring a JndiObjectFactoryBean
, because most of the properties are exposed on the common base classes. You will see this in practice in the next two sections.
Accessing a stateless session bean via a proxy
The easiest way to access a stateless session bean in a Spring application is to use a proxy. Spring provides two proxy FactoryBean
classes for stateless session beans: LocalStatelessSessionProxyFactoryBean
for local beans and SimpleRemoteStatelessSessionProxyFactoryBean
for remote beans.
Because the EchoServiceEJB
is only exposed through a local interface, we use LocalStatelessSessionProxyFactoryBean
in this example, but the configuration is identical for both classes. All you need to do to create the proxy is configure the appropriate FactoryBean
in your application configuration file.
Listing 16 shows the configuration for the echoService
bean, which is a proxy to the EchoServiceEJB
.
Listing 16. Configuring a stateless session bean proxy
<bean id="echoService"
class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>ejb/echoService</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
<property name="businessInterface">
<value>com.apress.prospring.ch13.ejb.EchoService</value>
</property>
</bean>
The first thing you should notice here is that two of the properties, jndiName
and resourceRef
, are identical to those used in JndiObjectFactoryBean
. This is because, as we described, LocalStatelessSessionProxyFactoryBean
and JndiObjectFactoryBean
share the same JNDI infrastructure base classes. The third property specified in the configuration, businessInterface
, tells the LocalStatelessSessionProxyFactoryBean
what interface the proxy should implement. In general, this is the business interface used for the EJB. You can use another interface, perhaps to limit the methods exposed, but be sure that the methods in your interface match methods in the local interface of the EJB. Remember that because LocalStatelessSessionProxyFactoryBean
is a FactoryBean
, Spring does not return the bean instance itself; instead, it returns the result of FactoryBean.getObject()
, which in this case, is the proxy to the EJB.
In the simple example servlet that we built, we placed all code related to the stateless EchoServiceEJB
in the doStatelessExample()
method (see Listing 17).
Listing 17. Working with the EJB proxy
private void doStatelessExample(ApplicationContext ctx, PrintWriter writer) {
// Access the EJB proxy
EchoService service = (EchoService) ctx.getBean("echoService");
writer.write(service.echo("Foo"));
}
Here you can see that the EchoServiceEJB
is accessed, via the proxy, as though it were just a standard Spring bean. Using this approach makes it simple to swap out EJBs for standard POJOs and vice versa without affecting any dependency components.
Simplifying stateful session bean access
When accessing stateful session beans, be aware that currently, no proxy classes are available for you to use. However, you can still simplify your code somewhat by using the JNDI support in Spring.
The first step is to configure a bean in your ApplicationContext
to access the EJB home interface, as shown in Listing 18.
Listing 18. Accessing EJB home using Spring
<bean id="counterServiceHome"class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>ejb/counterService</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
</bean>
Here we simply use the JndiObjectFactoryBean
to perform the lookup of the home interface automatically. In the code, we can avoid performing this lookup manually and instead access the home interface using Spring. Listing 19 shows the example code for the stateful session bean.
Listing 19. Using the CounterServiceEJB
private void doStatefulExample(ApplicationContext ctx, PrintWriter writer,HttpSession session) {
CounterService service = (CounterService) session.getAttribute("counterService");
if (service == null) {
try {
CounterServiceHome home = (CounterServiceHome) ctx.getBean("counterServiceHome");
service = (CounterService) home.create();
session.setAttribute("counterService", service);
} catch (CreateException ex) {
ex.printStackTrace(writer);
return;
}
}
writer.write("Counter: " + service.increment());
}
Notice that we are able to avoid performing the JNDI lookup. Instead, we rely on Spring to do that for us. Because we are storing the handle to the stateful session bean in the HttpSession
, subsequent requests to this servlet use the same instance and thus, we see the counter on the page increase.
EJB access summary
When using EJBs in your Spring-based applications, you have a lot of options for simplifying how you access them. When using stateful session beans, you can use Spring's JNDI support to simplify the EJB home interface lookup. For stateless session beans, you can go a few steps further and create a proxy to the EJB; this allows you to treat it as a POJO bean and frees you and your application from EJB-specific details.
Testing EchoService and CounterService
All that remains for this example is to finish off the example servlet and package up the application.
Finishing the EjbTestServlet
You have already seen the bulk of the code for the EjbTestServlet
class in the doStatelessExample()
and doStatefulExample()
methods shown earlier. All that is left is the doGet()
method, which loads the ApplicationContext
and invokes both doStatelessExample()
and doStatefulExample()
. The code for doGet()
is shown in Listing 20.
Listing 20. The doGet() method
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
response.setContentType("text/html");
writer.write("<html><head><title>EJB Samples</title></head>");
writer.write("<body>");
writer.write("<h1>Echo Service (Stateless Session Bean)</h1>");
doStatelessExample(ctx, writer);
writer.write("<h1>Counter Service (Stateful Session Bean)</h1>");
doStatefulExample(ctx, writer, request.getSession());
writer.write("</body></html>");
}
This code is fairly basic, so we do not need to go into great detail explaining it; the only point of note is that the ApplicationContext
is loaded in the init()
method.
Packaging the sample application
We are not going to go into great detail regarding the packaging of the sample application, but we do want to point out that it actually has two applicationContext.xml
files to configure Spring. The first, shown in Listing 21, goes inside the EJB JAR and is used by the EJBs for configuration.
Listing 21. ApplicationContext configuration for EJBs
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="echoService"class="com.apress.prospring.ch13.ejb.EchoServiceImpl"/>
<bean id="counterService"class="com.apress.prospring.ch13.ejb.CounterServiceImpl"/>
</beans>
Here you can see that the POJO implementations of the EchoService
and CounterService
interfaces are configured and made available for access by the EchoServiceEJB
and CounterServiceEJB
classes. The second applicationContext.xml
file sits in the WEB-INF
directory of the Web applications war file. This file, shown in Listing 22, contains the configuration for the EchoServiceEJB
proxy and the CounterServiceEJB
home interface lookup.
Listing 22. Configuring the sample application
<beans>
<bean id="echoService"class="org.springframework.ejb.access.
LocalStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>ejb/echoService</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
<property name="businessInterface">
<value>com.apress.prospring.ch13.ejb.EchoService</value>
</property>
</bean>
<bean id="counterServiceHome"class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>ejb/counterService</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
</bean>
</beans>
Running the sample application
Running the sample application is easy—just drop the ear file created by the build script into the deploy directory in JBoss and you are on your way. The first time the screen is displayed, it looks something like Figure 3.
Figure 3. First run of the sample application. Click on thumbnail to view full-sized image.
|
Here, you can see that the foo
message is echoed back to the screen by the EchoServiceEJB
and that the CounterServiceEJB
is currently displaying the count as one. Refreshing this screen results in the same message from EchoServiceEJB
, but the counter displayed by CounterServiceEJB
steadily increases.
EJB summary
In this article, we looked at Spring's support for EJB, specifically focusing on support for stateless and stateful session beans. Using Spring's base classes, you can easily create EJBs that are simply wrappers around dynamically loaded business logic. Using this approach makes for easier testing and looser coupling in your application. When accessing stateless session beans, Spring provides proxy support, freeing you of the coding burden of accessing EJB resources and decreasing the coupling in your application.
About the author
Rob Harrop is the lead architect of U.K.-based software company Cake Solutions Limited. As part of Cake Solutions' team, Harrop has planned, designed, and built enterprise-level applications for a variety of U.K.- and U.S.-based clients, including the Metropolitan Police, DTi, and National Union of Students Services Limited. In his spare time, he is an avid reader and enjoys playing with new technology, his current interests being Python and JavaServer Faces.
Jan Machacek is lead programmer of U.K.-based software company, Cake Solutions Limited, where he has helped design and implement enterprise-level applications for a variety of U.K.- and U.S.-based clients. In his spare time, Machacek enjoys exploring software architectures, nonprocedural and AI programming, and playing with computer hardware. Like a proper computer geek, Machacek loves the Star Wars and The Lord of the Rings series. He lives in Manchester in the U.K.
Resources
- This article excerpts Chapter 13, "Spring and J2EE," from Pro Spring, Rob Harrop, Jan Machacek (Apress, January 2005; ISBN: 1590594614):
http://java.apress.com/book/bookDisplay.html?bID=405
- Expert One-on-One J2EE without EJB, Rod Johnson and Juergen Hoeller (Wrox, 2004; ISBN: 0764558315):
http://www.amazon.com/exec/obidos/ASIN/0764558315/javaworld
- For more articles on EJB, browse the Enterprise JavaBeans (EJB) section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-ejbs-index.shtml
- For more on Spring, read "Let Your Ant Enjoy Spring," Josef Betancourt (JavaWorld, February 2005):
http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-antspring.html
- To learn how to use Spring with JSF, read "Put JSF to Work," Derek Yang Shen (JavaWorld, July 2004):
http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-jsf.html