Pro Spring: Spring and EJB#

<center>EJB와 Spring을 같이 사용하는 방법에 대해서 공부해 보자.</center>

개요#

전통적인 J2EE애플리케이션에서 EJB는 애플리케이션 구조의 기본으로 여겨저 왔다. 비록 Spring이 EJB에 의해서 제공되는 선언적 트랜잭션 관리와 객체영속성(persistence)같은 많은 서비스의 간단한 버전(구현)을 제공하지만 많은 애플리케이션은 어떤때를 위해서 EJB를 계속적으로 사용할것이다. 고맙게도 당신은 EJB기반 솔루션에서 Spring을 사용할수 있고 이 글은 Pro Spring이라는 이름의 책에서 발췌된것이다.

By Rob Harrop and Jan Machacek


Spring의 출현으로 지금 개발자들은 처음으로 EJB의 가벼운 대체물을 가지게 되었다. Spring을 사용하는 것은 EJB에 의해서 제공되는 선언적 트랜잭션 관리, 객체 풀링 그리고 간단한 ORM(object role modeling)기능과 같은 많은 기능의 장점을 가지게 되었다. 우리는 EJB가 다가오는 미래의 애플리케이션 개발을 위해 계속적으로 사용되리라고 예견했다. Spring을 사용하는 경험은 매우 놀라웠고 우리는 언제든 EJB대신에 그것을 사용하도록 권유한다. 우리가 이 글에서 중점을 두려고 하는것은 EJB를 사용해서 애플리케이션을 만들때에도 계속적으로 Spring을 쓰는 방법이다.

Spring에서 EJB지원#

Spring에서 EJB지원은 access와 implementation이라는 두가지 카테고리로 느슨하게 그룹화된다. access는 당신이 EJB자원접근을 더 쉽게 만들어주는 클래스를 제공한다. 이 부분에서 우리는 EJB접근을 쉽게하기 위한 프레임워크를 제공하는 기본적인 JNDI를 확장하는 방법과 EJB자원의 proxy기반 접근을 제공하는 AOP를 사용하는 방법에 대해서 볼것이다.

Spring에서 EJB implementation은 세가지 타입(stateless session beans, stateful session beans그리고 message-driven beans) 의 EJB를 생성하는 것을 좀더 쉽게 해주는 추상적인 기본 클래스들을 제공한다. 그런 기본 클래스 뒤의 기본 전제는 EJB생성의 짐을 쉽게 만드는데 충분하지는 않지만 당신의 빈즈내에서 Spring관리 자원에 쉽게 접근하도록 만들어주고 좀더 중요한것은 EJB구현에서 비지니스 로직을 가져와서 EJB에 의해 사용되는 POJO로 추가한다. 만약에 현 시점에 이것들이 제대로 이해가 되지 않더라도 걱정하지 말라. 우리는 이것들 제대로 이해하도록 두가지 예제를 통해 다음 섹션에서 상세한 사항에 대해 논의할것이다.

우리는 EJB서비스를 사용하는 간단한 웹 애플리케이션을 만들것이다. 첫번째는 EchoService비지니스 인터페이스를 구현하고 간단한 echo기능을 제공하는 비상태유지 세션빈이다. 두번째는 CounterService비지니스 인터페이스를 구현하고 카운팅을 위한 상태유지 서비스를 제공하는 상태유지 세션빈이다.

이것들은 별거 아닌 예제들이지만 그들은 Spring의 EJB지원에 관련된 다른 컴포넌트처럼 Spring과 함께 EJB를 생성하는데 추천되는 방식을 시연하는데 도움을 준다. 우리는 우리의 샘플을 만드는 여러가지 배치기술자에 대해서 논하는 것보다 EJB스펙의 많은 상세사항에 대해서 들어가지는 않는다. 우리는 어쨋든 여러가지 컴포넌트에서 Spring의 EJB지원에 대한 구현에 대해서 볼것이고 당신의 애플리케이션애 어떻게 영향을 줄지 볼것이다. 우리는 당신의 EJB를 위한 Spring이 어떻게 ApplicationContext에 위치하는지와 JNDI인프라스트럭쳐를 사용해서 어떻게 JNDI자원이 위치하는지에 대해서 볼것이다.

우리는 세가지 타입의 EJB를 Spring이 어떻게 지원하는지 본다고 언급했지만 우리는 비상태유지와 상태유지 두가지에 대해서만 볼것이다. 메시지 빈은 그 두가지와 비슷한 패턴을 사용한다.

Spring와 함께 EJB빌드하기#

Spring은 당신의 EJB빈 클래스(AbstractStatelessSessionBean, AbstractStatefulSessionBean, 그리고 AbstractMessageDrivenBean)를 위한 기본 클래스처럼 제공하는 세가지의 추상클래스를 제공한다. Spring을 사용해서 EJB를 빌드할때 당신은 여전히 인터페이스와 home클래스를 만들것이다. 하지만 빈 클래스를 구현할때 적합한 Spring기본 클래스에서 그 내용들을 끌어낸다. 그 기본 클래스는 EJB가 Spring ApplicationContext에 접근하기 위해 Spring에 의해 제공되고 Spring에 의해서 관리되는 자원에 접근하도록 허락한다.

Spring을 사용해서 우리의 EchoService와 CounterService 빈즈를 빌드하는 상세정보에 들어가기전에 우리는 Spring이 당신의 EJB를 위해 어떻게 ApplicationContext가 어떻게 위치 되는지 볼것이고 이것은 당신이 Spring을 사용할때 EJB를 빌드하기 위한 추천되는 방식이다.

Spring EJB클래스의 구조#

Spring는 그림 1에서 보여지는 것처럼 EJB지원 클래스를 위해 잘정의된 클래스 구조를 제공한다.

springejb1.jpg

Figure 1. Spring EJB support classes.

당신이 볼수 있는것처럼 중심의 기본 클래스인 AbstractEnterpriseBean은 하위 클래스가 BeanFactoryLocator 인스턴스에 접근하기 위해서 beanFactoryLocator 속성을 해설한다. BeanFactoryLocator 인터페이스는 다음 섹션에서 loadBeanFactory()메소드, unloadBeanFactory()메소드와 함께 좀더 다양하게 논의가 될것이다. AbstractStatelessSessionBean 클래스는 이미 ejbCreate(), ejbActivate(), 그리고 ejbPassivate() 메소드를 구현해 두었다. 그에 반해서 AbstractStatefulSessionBean 는 그렇지 않다.

BeanFactoryLocator 인터페이스#

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

springejb2.jpg

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 = (EchoServicegetBeanFactory().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 = (CounterServicegetBeanFactory().getBean("counterService");
   }

   public void ejbActivate() throws EJBException, RemoteException {
      load();
      service = (CounterServicegetBeanFactory().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 = (EchoServicectx.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 = (CounterServicesession.getAttribute("counterService");

   if (service == null) {

      try {
         CounterServiceHome home = (CounterServiceHomectx.getBean("counterServiceHome");

         service = (CounterServicehome.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 responsethrows 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.

springejb3.jpg

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.

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
jpg
springejb1.jpg 60.2 kB 1 06-Apr-2006 09:45 이동국
jpg
springejb2.jpg 42.0 kB 1 06-Apr-2006 09:45 이동국
jpg
springejb3.jpg 27.6 kB 1 06-Apr-2006 09:45 이동국
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor