Accessing a JBoss EJB3 Session Bean With Spring
Spring and Hibernate have long been my choice of frameworks with which to construct maintainable, scalable middle-tier software. Spring promotes good OO design using loose coupling and provides excellent declarative transaction support. Hibernate is the persistence tool of choice for the open source community. Any sane person who programs this way will have given up on EJB long ago. Coarse-grained entities, tightly coupled service objects and XML deployment descripters a-plenty are enough to bring a guy to his knees.
But no more. The EJB expert group, now composed of some of the very creators of modern persistence framework, has bequeathed upon us something wonderful. The lightweight POJO programming model we have come to love is apparent everywhere in the EJB 3.0 specifiction (JSR-220), which was unanimously accepted by the JCP earlier this year. EJB 3.0 is just one goodie in the larger Java EE 5 (JSR-244) bag - but this post is going to be long enough as it is.
When I returned from Las Vegas with my newfound interest in EJB, my first task at hand was to create a little test application that demonstrated the scenario that is most applicable to my work: EJB3 services remoted over RMI consumed with POJO web applications that already use Spring. This use case stems from the need that we have realized the need to segment a growing middle-tier application from the monolithic beast it is destined to become into smaller, more manageable services. The client programs (mostly web applications) already access the business interfaces using Spring.
I'm going to skip the gory details of setting up the server, creating a project and all the like. There are plenty of explanations of this elsewhere. Besides, this post is going to be long enough as it is.
First we need a business interface. How about something really complex:
-
public interface SimpleService {
-
}
Nothing fancy there. Now let's implement it with some logic:
-
public class SimpleServiceBean implements SimpleService {
-
return string == null ? 0 : string.length();
-
}
-
}
Still totally straightforward. Now let's turn our bean into an EJB. This is the really complicated part. Watch for the annotations:
Did you get all that? The @Stateless annotation tells us that this is a Stateless Session bean, and the @Remote annotation specifies the remote interface. Everything else is handled by the container. Although the footwork is managed by the container in a somewhat vendor-specific way, the annotations are standard Java EE, and so you're not tied to an implementation. When you catch your breath, let's move on.
Since Java EE 5 has embraced configuration by exception, we don't even need any depoyment descriptors. These two files can be compiled, JAR'd up and deployed to a server of your choosing -- I used JBoss 4.0.4.
In order to have a client, we need to provide, at a minimum, the remote interfaces and any other classes or exceptions it references. For testing, I just tossed the same JAR on the classpath since I know it contains everything I need. Additionally, we need the Spring JARs (this test used 2.0RC1) and the JBoss client JAR's.
The only real programming we have to do here is to create a Spring container that wires up to the server's JNDI directory. To do this, we'll use Spring's JndiObjectFactoryBean. This part actually confused me because try as I did, I could never accomplish this feat using the more aptly-named SimpleRemoteStatelessSessionProxyFactoryBean. The difference, apparently, is that since EJB3 places no specific requirements on the business, home or remote interfaces, there is less to do, and thus the client simply grabs the object and narrows it down as prescribed. Here is my Spring configuration (minus the XML header and DTD info):
-
<bean id="simpleService" class="org.springframework.jndi.JndiObjectFactoryBean">
-
<property name="jndiEnvironment">
-
<props>
-
<prop key="java.naming.factory.initial">org.jnp.interfaces.NamingContextFactory</prop>
-
<prop key="java.naming.factory.url.pkgs">org.jboss.naming:org.jnp.interfaces</prop>
-
<prop key="java.naming.provider.url">jnp://localhost:1099</prop>
-
</props>
-
</property>
-
<property name="jndiName" value="SimpleServiceBean/remote" />
-
</bean>
The JNDI properties provided are JBoss specific, as is the provider URL. These are included with the JBoss client JAR in the form of a jndi.properties file. They are exactly the same as what would be used to construct a traditional InitialContext if this were a non-spring J2SE client.
JBoss mounts Remote interfaces (by default) using /remote, and Local interfaces using /local. As you can see here, we've chosen the remote. One could just as easily expose SimpleServiceBean as a Local EJB by using the @Local annotation and providing the same business interface as an arguement. The Spring container changes only in URL and jndiName values.
Once the bean factory is configured, you can load it and request the remote SimpleServiceBean proxy by name (simpleService), casting it to SimpleService as you would normally do with a Spring-managed object. It works flawlessly.