|
Implementing EJBs
If you choose to use EJB, Spring can provide important benefits in both EJB
implementation and client-side access to EJBs.
It's now widely regarded as a best practice to refactor business logic into
POJOs behind EJB facades. (Among other things, this makes it much easier to unit
test business logic, as EJBs depend heavily on the container and are hard to
test in isolation.) Spring provides convenient superclasses for session beans
and message driven beans that make this very easy, by automatically loading a
BeanFactory based on an XML document included in the EJB Jar file.
This means that a stateless session EJB might obtain and use a collaborator
like this:
import org.springframework.ejb.support.AbstractStatelessSessionBean;
public class MyEJB extends AbstractStatelessSessionBean implements MyBusinessInterface { private MyPOJO myPOJO;
protected void onEjbCreate() { this.myPOJO = getBeanFactory().getBean("myPOJO"); }
public void myBusinessMethod() { this.myPOJO.invokeMethod(); } }
Assuming that MyPOJO is an interface, the implementing class - and any
configuration it requires, such as primitive properties and further
collaborators - is hidden in the XML bean factory definition.
We tell Spring where to load the XML document via an environment variable
definition named ejb/BeanFactoryPath in the standard ejb-jar.xml deployment descriptor, as
follows:
<session> <ejb-name>myComponent</ejb-name> <local-home>com.test.ejb.myEjbBeanLocalHome</local-home> <local>com.mycom.MyComponentLocal</local> <ejb-class>com.mycom.MyComponentEJB</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>/myComponent-ejb-beans.xml</env-entry-value></env-entry> </env-entry> </session>
The myComponent-ejb-beans.xml file will be loaded from the classpath: in this
case, in the root of the EJB Jar file. Each EJB can specify its own XML
document, so this mechanism can be used multiple times per EJB Jar file.
The Spring superclasses implement EJB lifecycle methods such as
setSessionContext() and ejbCreate(), leaving the application developer to
optionally implement the Spring onEjbCreate() method.
When EJB 3.0 is available in public draft, we will offer support for the use
of the Spring IoC container to provide richer Dependency Injection semantics
in that environment. We will also integrate the JSR-220 O/R mapping API with
Spring as a supported data access API.
Using EJBs
Spring also makes it much easier to use, as well as implement EJBs. Many EJB
applications use the Service Locator and Business Delegate
patterns. These are better than spraying JNDI lookups throughout client code,
but their usual implementations have significant disadvantages. For example:
-
Typically code using EJBs depends on Service Locator or Business Delegate
singletons, making it hard to test.
-
In the case of the Service Locator pattern used without a Business Delegate,
application code still ends up having to invoke the create() method on an
EJB home, and deal with the resulting exceptions. Thus it remains tied to
the EJB API and the complexity of the EJB programming model.
-
Implementing the Business Delegate pattern typically results in significant
code duplication, where we have to write numerous methods that simply call
the same method on the EJB.
For these and other reasons, traditional EJB access, as demonstrated in
applications such as the Sun Adventure Builder and OTN J2EE Virtual Shopping
Mall, can reduce productivity and result in significant complexity.
Spring steps beyond this by introducing codeless business delegates.
With Spring you'll never need to write another Service Locator, another JNDI
lookup, or duplicate methods in a hand-coded Business Delegate unless you're
adding real value.
For example, imagine that we have a web controller that uses a local EJB.
We'll follow best practice and use the EJB Business Methods Interface
pattern, so that the EJB's local interface extends a non EJB-specific business
methods interface. (One of the main reasons to do this is to ensure that
synchronization between method signatures in local interface and bean
implementation class is automatic.) Let's call this business methods interface
MyComponent. Of course we'll also need to implement the local home interface
and provide a bean implementation class that implements SessionBean and the
MyComponent business methods interface.
With Spring EJB access, the only Java coding we'll need to do to hook
up our web tier controller to the EJB implementation is to expose a setter
method of type MyComponent on our controller. This will save the reference as
an instance variable like this:
private MyComponent myComponent;
public void setMyComponent(MyComponent myComponent) { this.myComponent = myComponent; }
We can subsequently use this instance variable in any business method.
Spring does the rest of the work automatically, via XML bean definition
entries like this. LocalStatelessSessionProxyFactoryBean is a generic factory
bean that can be used for any EJB. The object it creates can be cast by Spring
to the MyComponent type automatically.
<bean id="myComponent" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="myComponent" /> <property name="businessInterface" value="com.mycom.MyComponent" /> </bean>
<bean id="myController" class = "com.mycom.myController" > <property name="myComponent" ref="myComponent" /> </bean>
There's a lot of magic happening behind the scenes, courtesy of the Spring AOP
framework, although you aren't forced to work with AOP concepts to enjoy the
results. The "myComponent" bean definition creates a proxy for the EJB, which
implements the business method interface. The EJB local home is cached on
startup, so there's normally only a single JNDI lookup. (There is also support
for retry on failure, so an EJB redeployment won't cause the client to fail.)
Each time the EJB is invoked, the proxy invokes the create() method on the local
EJB and invokes the corresponding business method on the EJB.
The myController bean definition sets the myController property of the
controller class to this proxy.
This EJB access mechanism delivers huge simplification of application code:
-
The web tier code has no dependence on the use of EJB. If we want to replace
this EJB reference with a POJO or a mock object or other test stub, we could
simply change the myComponent bean definition without changing a line of
Java code
-
We haven't had to write a single line of JNDI lookup or other EJB plumbing
code as part of our application.
We can also apply the same approach to remote EJBs, via the similar
org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean
factory bean. However, it's trickier to conceal the RemoteExceptions on the
business methods interface of a remote EJB. (Spring does let you do this, if
you wish to provide a client-side service interface that matches the EJB
remote interface but without the "throws RemoteException" clause in the method
signatures.)
|