Accessing EJBs


Spring can provide a lot of help to client code that needs to access and use EJBs, both by reducing duplication of boilerplate code used to access EJBs, and also by allowing (stateless) EJBs to be used by clients as if they were any other service accessed via a business interface, with configuration happening in an IoC fashion.

Let's briefly review the standard, non-Spring approach to accessing and using an EJB.

The Boilerplate Approach

The standard approach to looking up and using an EJB is well known, if somewhat laborious. Consider the following sequence, for a (remote) Session bean:

Lookups happen via JNDI, so an InitialContext is first created:

 try {   InitialContext ctx = new InitialContext(); 

In this case, the client code is running inside the appserver, so no configuration of the InitialContext needs to be done. However, if the client is remote, then the client-side InitialContext needs to be configured so that it can remotely access the server-side JNDI environment. This may be done by passing into the context constructor, as properties in a Properties object, values specifying the InitialContext factory class, and the URL to access the remote server at:

 Properties props = new Properties(); props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); props.put(Context.PROVIDER_URL, "iiop://localhost:2809/"); 

This example uses standard IIOP wire protocol, but some containers, such as WebLogic, have optimized wire protocols that may be preferred. Alternately these values may be specified in a jndi.properties file visible on the client classpath.

Once the context has been created, it is used to look up the EJB home object. Calling create() on the home object returns the EJB remote proxy or stub:

 SimpleHomeRemote simpleHome = (SimpleHomeRemote) PortableRemoteObject         .narrow(ctx.lookup("Simple"), SimpleHomeRemote.class); SimpleRemote simple = simpleHome.create(); 

At this point, the business method may be called on the stub:

 simple.echo("hello"); 

The client-side invocation on the stub is marshaled to the server side if necessary (even when using remote EJBs, the server may actually internally optimize a remote call to a bean in the same VM to just a local method call), where the EJB server obtains an instance of the Stateless Session bean from the pool of beans it keeps ready, and invokes the actual method on the bean, which is then returned to the pool.

The client then must destroy the stub, by calling remove() on it, and also deal with any exceptions during the whole process, most of which are non-recoverable.

   simple.remove(); } catch (RemoteException e) {   ...     // handle } catch (NamingException e) {   ...     // handle } catch (CreateException e) {   ...     // handle } catch (RemoveException e) {   ...     // handle } 

This is a significant amount of code for what is ultimately a single method call on a business interface. Moving to a local EJB doesn't simplify things significantly, as it just removes the need to deal with RemoteException.

In a properly designed application, business logic code would of course delegate to some common utility methods in a helper class or superclass to perform some of this work, reducing some of the duplicate code. It is, however, hard to get away from the fact that the EJB client has to know it is dealing with an EJB, which needs to be looked up, and has to deal with some non-recoverable exceptions whether it wants to or not. Managing configuration data is also problematic. The general lookup and usage mechanism does not encourage a coding style that makes it easy or possible to test the EJB client code outside of the EJB container since JNDI access and EJB interfaces are hard to stub or mock. It is possible of course, with proper diligence toward separation of concerns, but much harder than it should be. This is a shame because complexity aside, an in-appserver test compared to an outside of the appserver unit test can mean the difference between, for example, a 2-minute build/deploy/test cycle and a 2-second one.

The Spring Approach

Spring provides two mechanisms that can make accessing and using an EJB easier. One is to encapsulate the Home object lookup so it can be handled in a declarative fashion, and the result injected into the client code that needs the Home. The other, usable for Stateless Session Beans, is to encapsulate, via a dynamically generated proxy object, the lookup of both the Home object and the EJB stub. This proxy can then be injected into client code to be used by the client, which is unaware that it is calling anything more than a business interface on a POJO service.

Encapsulating the Home Object Lookup

In Chapter 3, you were introduced to the JndiObjectFactoryBean, a factory bean that, as its output, returns the result of a JNDI lookup. Use of JndiObjectFactoryBean allows an EJB client, which needs a Home object, to be provided with that Home in an IoC fashion. Consider the following EJB client bean:

public class SimpleServiceClient {       private SimpleHomeRemote simpleHome;   public void setSimpleHome(SimpleHomeRemote simpleHome) {     this.simpleHome = simpleHome;   }       public void useSimpleService() {         try {       SimpleRemote simple = simpleHome.create();       simple.echo("hello");     }     catch (RemoteException e) {       ...     // handle     }     catch (CreateException e) {       ...     // handle     }     finally {       simple.remove();     }   } }

The EJB client deals with the Home only in terms of the remote (or local, as the case may be) Home interface. In a bean factory or application context definition, we configure the client like any other bean, feeding it as the simpleHome property, a reference to the JndiObjectFactoryBean:

 <bean  >   <property name="jndiName"><value>Simple</value></property> </bean>      <bean  >   <property name="simpleHome"><ref local="simpleBeanHome"/></property> </bean> 

You will sometimes come across a problem related to the particular loading sequence of EJBs versus other artifacts such as web-apps, in some J2EE appservers. The J2EE and EJB specifications do not satisfactorily address this common requirement. JndiObjectFactoryBean normally performs the JNDI lookup immediately after instantiation, caching the result. In an application context, which pre-instantiates singletons not marked as lazy-init, this will happen when the application context is initialized, and this will normally happen when the web-app starts up. It is possible in some appservers for a web-app to start up before EJBs are all loaded and made available in the JNDI environment. This loading sequence can make it possible for a JNDI lookup to be attempted for a resource that is not yet available! To avoid this problem, it is possible to set JndiObjectFactoryBean's lookupOnStartup property to false.

 <bean  >   <property name="jndiName"><value>Simple</value></property>   <property name="lookupOnStartup"><value>false</value></property>   <property name="proxyInterface">   <value>org.springframework.autobuilds.ejbtest.simple.ejb.SimpleHomeRemote</value>   </property> </bean> 

This will force the factory bean to generate a proxy object to stand in for the real JNDI object, which does the actual JNDI lookup only on first use. Note that you do have to specify the interface of the JNDI object (the EJB home in this case) because the proxy has to be generated to match the JNDI object, which has not yet been retrieved. If you need to generate many lazy JNDI proxies like this, remember that, as described in Chapter 3, you may use the parent/child bean definition technique to reduce duplicate configurations by inheriting from an abstract template definition, which sets some properties and attributes that never vary. For example:

 <bean  abstract="true"       >   <property name="lookupOnStartup"><value>false</value></property> </bean> 

The preceding example did not configure the JNDI environment, assuming the code is run inside an appserver, or the properties are obtained from the classpath in a jndi.properties file as mentioned previously as one config option. The JndiProxyFactoryBean configuration may, however, specify configuration values for the environment, via the jndiEnvironment property.

An alternate solution is to set both the JndiObjectFactoryBean and its users (clients) to lazy-init. This avoids the need to specify a proxyInterface (because there is no proxy needed), but is somewhat unreliable because the whole chain of related beans needs to be marked lazy.

The encapsulation of the Home lookup is useful for client access to both Stateless and Stateful Session beans. For the latter, it is the only client-side help that Spring offers. It would be hard for Spring to do much more because the actual EJB stub needs to be obtained by calling one of the potentially many custom create() method variants that an SFSB may define. Additionally, the SFSB instance needs to be used and managed by just one client, potentially over a longer time period, with remove() being called on the instance when it is no longer needed.

Encapsulating Stateless Session Bean Lookups

A general EJB best-practice is for clients of Stateless Session Beans to deal with EJBs through a business interface only. Consider an example business interface for a local EJB:

 public interface SimpleService1 {   public String echo(String input);   public String echo2(String input);   public String echo3(String input); } 

The following example takes the same approach for a remote EJB:

 public interface SimpleService2 extends java.rmi.Remote {   public String echo(String input) throws RemoteException;   public String echo2(String input) throws RemoteException;   public String echo3(String input) throws RemoteException; } 

The client code working against these interfaces may be completely unaware that it is dealing with an EJB. In the case of the remote EJB, it is aware that it's dealing with a remote API but doesn't care how that's implemented. This makes testing and changing the implementation much easier.

For the local EJB case, the EJB local interface derives from both the business interface and the standard EJBLocalObject interface:

 public interface SimpleLocal extends EJBLocalObject, SimpleService1 {  } 

For the remote EJB case, the EJB remote interface derives from both the business interface and the standard EJBObject interface:

 public interface SimpleRemote extends EJBObject, SimpleService2 {  } 

Aside from the fact that the client code is calling business methods through a business interface only, increasing decoupling and making testing easier, the other benefit of using this pattern is on the EJB implementation side: a normal local or remote EJB implementation class may not implement the local or remote interfaces that the client ends up using. They are meant for client-side use, but with this pattern, implementation classes can be forced to implement the business interface, which means that all signatures for methods that will be called by client code will automatically be verified.

Important 

For these reasons, we recommend that you always use the business interface pattern (see http://c2.com/cgi/wiki?BusinessInterface) when accessing Session beans. This should be the case with or without Spring.

If you are willing to use this pattern, when you do use Spring it also means that Spring can be of major help in simplifying lookup and access to Stateless Session beans, as we're going to describe now.

Accessing Local EJBs

Assume that you have EJB client code that deals with a local EJB through a business interface only:

 public class SimpleServiceClient {        private SimpleService1 simple;        public void setSimpleHome(SimpleService1 simple) {     this.simple = simple;   }        public void useSimpleService() {     simple.echo("hello");   } } 

Spring makes it easy, via a factory bean called LocalStatelessSessionProxyFactoryBean, to create and inject into that EJB client, an object implementing the business interface, which actually encapsulates lookup of the EJB Home and EJB. Here's a configuration example:

 <bean       >   <property name="jndiName">     <value>local/Simple</value>   </property>   <property name="businessInterface">     <value>org.springframework.autobuilds.ejbtest.simple.SimpleService1</value>   </property> </bean>        <bean  >   <property name="simpleService"><ref local="simpleBean"/></property> </bean> 

The LocalStatelessSessionProxyFactoryBean instance is configured with the JNDI location of the EJB local home, as well as the business interface, SimpleService1 in this case. The proxy factory bean produces as its output a proxy object that implements the specified business interface. In a default configuration, at the time the proxy is created, the EJB Home object is looked up and cached. When a client invokes a business interface method on the proxy, it creates an instance of the Session bean by calling create() on the cached Home. It then invokes the business method on the Session Bean and returns the result, making sure the Session Bean instance is destroyed first by calling remove() on it.

Therefore, as far as the client is concerned, invoking the EJB method in this fashion is no different than if a method on a regular stateless POJO service object were being called. This points out one of the great benefits of this approach. Aside from the reduction in boilerplate access code, it makes it trivial to change the implementation for the service the client is using. Swapping out the EJB implementation and dropping in a POJO version, or vice-versa, can be done without the client being aware.

Note that a number of aspects can be customized by configuring or subclassing the proxy factory bean. As described for the JndiObjectFactoryBean, you may want to set the lookupHomeOnStartup property to false, to have the Home object be lazy loaded on first use, instead of on proxy creation, and avoid potential failure if the particular container running the app loads EJBs after the web-app containing the application context containing the proxy is created. Setting the cacheHome property to false will force the proxy to look up the Home object on every invocation, instead of caching it. This is helpful for development scenarios because the client will no longer hold on to a possibly out-of-date Home object after a new version has been deployed into the appserver.

Accessing Remote EJBs

A similar factory bean, SimpleRemoteStatelessSessionProxyFactoryBean, exists for producing proxies to look up and access remote Stateless Session Beans. Assuming we are now using the remote SimpleService2 business interface instead of the local SimpleService1 interface, the EJB client is(aside from the different interface name introduced in this example for clarity) almost unchanged, except for the fact that it has to deal with RemoteException:

... // unchanged       public void useSimpleService() {     try {       simple.echo("hello");       catch {RemoteException e} {       ...   // handle       }     }   }

The application context definition is updated to use the remote proxy factory, to specify the appropriate remote SimpleService business interface, and to specify the appropriate JNDI location:

<bean    background-color:#c0c0c0">">   <property name="jndiName">     <value>Simple</value>   </property>   <property name="businessInterface">     <value>org.springframework.autobuilds.ejbtest.simple.SimpleService2</value>   </property> </bean>       <bean  >   <property name="simpleService"><ref local="simpleBean"/></property> </bean>

Accessing Remote EJBs, Unifying the Business Interface

The local and remote Session Bean access mechanisms are very useful, and work well, but there remains one annoyance, which would ideally also be resolved by Spring, and this is the fact that the business interface for the local EJB has to be different from that of the remote EJB, which must extend java.rmi.Remote, and throw RemoteException on every method. This makes it impossible for client code directly accessing the EJB to be agnostic to whether the EJB is local or remote. This is also annoying because often remote exceptions are not recoverable by the client code using a business method anyway, and are better handled by an ancestor. (Where methods are idempotent, they are better marked as such and retried directly by the appserver, if the appserver is able to do this.)

Spring can help. Remember that while the EJB remote interface must have throws RemoteException on method signatures, the actual EJB implementation class doesn't extend the remote interface anyway; it extends SessionBean and ideally the business interface. This means that instead of having separate local and remote business interfaces, we can have just one business interface (identical to the previous local interface), without any remoting aspects. Then, when creating the remote EJB class, we implement the following (non-remote) business interface:

 public interface SimpleService {   public String echo(String input);   public String echo2(String input);   public String echo3(String input); }     // implement the EJB public class SimpleEJBWithCmtAndNoSpringTx implements SessionBean, SimpleService {   ... } 

The EJB container is still perfectly happy.

We now have a problem that the remote interface can no longer derive from the business interface, as it still needs to throw RemoteException on every method. The solution is to keep a separate remote interface in synch with the business interface:

 public interface SimpleRemote extends EJBObject {   public String echo(String input) throws RemoteException;   public String echo2(String input) throws RemoteException;   public String echo3(String input) throws RemoteException; } 

The final question concerns how to reconcile the fact that though the client should be dealing with the business interface, the remote interface no longer even extends from it, and the EJB stub with which the client works implements the remote interface, not the business interface. This is where SimpleRemoteStatelessSessionProxyFactoryBean comes in. Without client code having to do anything, if the client works against a business interface and calls a method having the same signature as a method in the remote interface except that RemoteException is not thrown, the proxy will map method invocations from one interface to the other when it internally invokes the method on the EJB stub.

If the EJB container does throw a java.rmi.RemoteException when invoking a method on a remote EJB, Spring will simply re-throw it as Spring's own, non-checked RemoteAccessException. Client code is still free to catch and deal with this, in a similar fashion to how it might want to deal with RemoteException. However, there is the added benefit that because it is a non-checked exception, client code, which cannot deal with the remote exception, doesn't have to add a boilerplate catch clause or throws declaration.

The one negative aspect is the need to keep the business interface in synch with the remote interface, but this is usually easily handled by simple cutting and pasting with the addition of a throws RemoteException on each method. This negative aspect pales in comparison to the advantages of using this approach. (Note that the need to keep in-synch classes with matching method signatures but no common parent is a common problem in EJB, with the need to synchronize component interface and EJB implementation classes. The inelegance here comes from the EJB specification — and RMI — rather than Spring, which allows normal use of interfaces in other cases.)

Important 

This mechanism is very convenient because it means the client can always work against just one business interface used both for local and remote EJBs. The service implementation may be local or remote as initially makes sense, and switched later as needed without client changes.

This mechanism, combined with the general encapsulation of the EJB lookup mechanism described earlier, additionally makes it possible for EJBs to be a completely"use as needed" implementation technology. The client works simply against a business interface, and the actual implementation of the service may be a POJO, remote POJO marshaled via another Spring remoting technology, local EJB, or remote EJB, as makes sense for the particular circumstances.



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

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