This is the third and for now last part of my series on Google's dependency injection (DI) framework Guice. The first part showed how to get started with Guice in 15 minutes, while the second installment explained Guice's concept of method interception.
Today I want to show how to integrate Guice with EJB. One might ask, whether that makes sense at all, as EJB 3 provides a DI container and an interceptor framework itself. But these services are only available for beans, which are managed by the container. You can't use DI and method interception for non-managed objects, and even within managed beans only certain objects (such as other session beans or JPA EntityManagers) can be injected.
But luckily Guice comes to the rescue and allows to use DI and method interception in and with arbitrary objects.
The example: Retrieving online weather information
As example for the integration of Guice and EJB in the following an application shall be built, that is able to retrieve the weather information (that is temperature and a textual description of the weather conditions) for a given location, using the freely available Yahoo! Weather RSS Feed.
We will encapsulate the access to the Yahoo! service in a dedicated client class. To allow for mocking the online requests in unit tests, we introduce an interface WeatherService that for now will be implementated by the actual Yahoo! client. The service interface just provides one method, getWeatherCondition():
1 2 3 4 5 6 |
@ImplementedBy(WeatherServiceYahooImpl.class) public interface WeatherService { WeatherCondition getWeatherCondition(String locationCode); } |
WeatherCondition is a simple model class, that contains the weather information for one location:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package de.gmorling.guiceonejb.demo; public class WeatherCondition { final String text; final int temperature; final String location; public WeatherCondition(String location, String text, int temperature) { this.location = location; this.text = text; this.temperature = temperature; } //getters and setters, equals() and hashCode() ... } |
Now let's build the implementation of the WeatherService, based on the Yahoo! feed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package de.gmorling.guiceonejb.demo; import java.io.InputStream; import java.net.URL; import org.jdom.Document; import org.jdom.Element; import org.jdom.Namespace; import org.jdom.input.SAXBuilder; public class WeatherServiceYahooImpl implements WeatherService { private final static String address = "http://weather.yahooapis.com/forecastrss?u=c&p="; @Override public WeatherCondition getWeatherCondition(String locationCode) { InputStream inputStream = null; try { inputStream = new URL(address + locationCode).openStream(); Document doc = new SAXBuilder().build(inputStream); Namespace namespace = Namespace.getNamespace("http://xml.weather.yahoo.com/ns/rss/1.0"); Element channelElement = doc.getRootElement().getChild("channel"); Element locationElement = channelElement.getChild("location", namespace); Element conditionElement = channelElement.getChild("item").getChild("condition", namespace); return new WeatherCondition( locationElement.getAttributeValue("city") + ", " + locationElement.getAttributeValue("country"), conditionElement.getAttributeValue("text"), Integer.parseInt(conditionElement.getAttributeValue("temp"))); } catch (Exception e) { throw new RuntimeException(e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (Exception e) {} } } } } |
The implementation is pretty straight-forward. First we retrieve the weather information from the Yahoo! feed by opening an InputStream to the feed URL. Using the JDOM API we then create a Document object representing the returned RSS structure. From the child elements of that document we can retrieve all the information required to create a new WeatherCondition instance.
Writing a session bean
Having implemented the weather service, let's now create a stateless session bean, that uses this service. The bean in the following example just delegates all calls to the actual service, but of course one could imagine a more complex bean with its own logic etc. First we have to create the local interface for the session bean. Thanks to EJB 3 this is just a POJI (plain old java interface) annotated with @Local:
1 2 3 4 5 6 7 8 9 10 11 12 |
package de.gmorling.guiceonejb.demo.ejb; import javax.ejb.Local; import de.gmorling.guiceonejb.demo.WeatherCondition; @Local public interface WeatherInformationLocal { WeatherCondition getWeatherCondition(String locationCode); } |
Next we create the session bean by implementing the local interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package de.gmorling.guiceonejb.demo.ejb; import javax.ejb.Stateless; import com.google.inject.Inject; import de.gmorling.guiceonejb.UsesGuice; import de.gmorling.guiceonejb.demo.WeatherCondition; import de.gmorling.guiceonejb.demo.WeatherService; @Stateless @UsesGuice public class WeatherInformationBean implements WeatherInformationLocal { @Inject private WeatherService weatherService; @Override public WeatherCondition getWeatherCondition(String locationCode) { return weatherService.getWeatherCondition(locationCode); } } |
To create a stateless session bean, we just annotate the class with the @Stateless annotation. Besides that, two things here are remarkable:
- The injection of a WeatherService instance using Guice's @Inject annotation
- The session bean being annotated with a new annotation, @UsesGuice
Bringing together Guice and EJB
How comes, that the weatherService field is obviously populated by Guice? This is realized by using an EJB interceptor (very similar to Guice's method interceptors, as described in the second installment of this series), that hooks into each call of the session bean's methods by the container and injects all fields annotated with @Inject. Let's have a look at that interceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package de.gmorling.guiceonejb; import javax.ejb.EJB; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import com.google.inject.Injector; public class GuiceInterceptor { @EJB private GuiceInjectorHolder injectorHolder; @AroundInvoke public Object injectByGuice(InvocationContext ctx) throws Exception { if (ctx.getTarget().getClass().isAnnotationPresent(UsesGuice.class)) { Injector injector = injectorHolder.getInjector(); injector.injectMembers(ctx.getTarget()); } return ctx.proceed(); } } |
By annotating the method injectByGuice() with @AroundInvoke, we specify, that the interceptor shall be called for each method invocation on the target bean by the container. Within the method, we first check, whether the target bean is annotated with @UsesGuice. If that's the case, we obtain a Guice injector and use it to perform the dependency injection on the target bean. Finally we continue with the intercepted call by invoking the proceed() method of the passed invocation context.
Having implemented the interceptor, we must register it with the EJB container and specify, for which session beans it shall be invoked. As we want to enable Guice DI in basically any session bean, we do this by using the ejb-jar.xml deployment descriptor as follows (another option would be to annotate only selected beans with @Interceptors):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd" version="3.0"> <interceptors> <interceptor> <interceptor-class>de.gmorling.guiceonejb.GuiceInterceptor</interceptor-class> </interceptor> </interceptors> <assembly-descriptor> <interceptor-binding> <ejb-name>*</ejb-name> <interceptor-class>de.gmorling.guiceonejb.GuiceInterceptor</interceptor-class> </interceptor-binding> </assembly-descriptor> </ejb-jar> |
One question is still unanswered: where did we retrieve the Guice injector from? We got it from another session bean, called GuiceInjectorHolder (which we get injected by the EJB container, as EJB in contrast to Guice allows DI also within interceptors).
The reason for this is, that we want to use the same Guice injector throughout the entire application. That's required in order to support Guice's singleton scope, that allows to specify objects to be singletons but only within the context of one and the same injector instance.
This makes the Guice injector for our purposes actually a singleton object itself. Singletons used to be rather problematic in traditional JEE applications (mostly they are realized using static fields), but the upcoming EJB version 3.1 simplifies that matter by introducing the concept of singleton beans. That's very useful for our GuiceInjectorHolder, which looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
//interface package de.gmorling.guiceonejb; import com.google.inject.Injector; public interface GuiceInjectorHolder { Injector getInjector(); void setInjector(Injector injector); } //implementation package de.gmorling.guiceonejb; import javax.ejb.Singleton; import javax.interceptor.ExcludeDefaultInterceptors; import com.google.inject.Guice; import com.google.inject.Injector; @Singleton @ExcludeDefaultInterceptors public class GuiceInjectorHolderBean implements GuiceInjectorHolder { private Injector injector = Guice.createInjector(); @Override public Injector getInjector() { return injector; } @Override public void setInjector(Injector injector) { this.injector = injector; } } |
All we have to do is to annotate the bean implementation with @Singleton. This annotation is already supported by GlassFish V3 as well as by Apache OpenEJB, which we will use for unit testing purposes later on.
Annotating the bean with @ExcludeDefaultInterceptors ensures, that the GuiceInterceptor won't be applied to that bean, as there are no dependencies to be injected here. The setInjector() method allows to set the injector to be used, by default a standard injector without any configuration modules will be used.
Writing a unit test
That completes the Guice-EJB integration layer (comprising the @UsesGuice annotation, the GuiceInterceptor and the GuiceInjectorHolder bean). So it's time to give it a try in a unit test.
For unit testing our EJB application we can use the embeddable OpenEJB container. It allows to launch a fully functional EJB container within a unit test, without the need to install an application server, to perform a deployment before the test etc. as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package de.gmorling.guiceonejb; import static org.junit.Assert.*; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.google.inject.Guice; import de.gmorling.guiceonejb.demo.WeatherCondition; import de.gmorling.guiceonejb.demo.ejb.WeatherInformationLocal; public class GuiceInterceptorTest { private InitialContext context; @Before public void setUp() throws Exception { Properties properties = new Properties(); properties.setProperty( Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory"); properties.setProperty( "openejb.deployments.classpath.include", ".*guiceonejb/target/classes.*"); properties.setProperty( "openejb.embedded.initialcontext.close", "destroy"); context = new InitialContext(properties); } @Test public void getWeatherInformationFromYahoo() throws Exception { WeatherInformationLocal bean = (WeatherInformationLocal) context.lookup("WeatherInformationBeanLocal"); assertNotNull(bean); WeatherCondition weatherCondition = bean.getWeatherCondition("GMXX0025"); assertEquals("Dresden, GM", weatherCondition.getLocation()); } @After public void tearDown() throws Exception { context.close(); } } |
All we have to do is to setup an InitialContext as shown within the setUp() method. This context can be used to retrieve an instance of the WeatherInformationBean. The call to getWeatherCondition() will be intercepted by the container, the WeatherService implementation based on the Yahoo! feed will be injected by Guice and the weather information for the specified location code will be retrieved.
Creating a mock implementation
Calling external services from within a unit test is mostly not a good idea (test success is subject to the service's availability, it will make the test running longer etc.). Therefore we will create a mock implementation of the WeatherService, that can be used in the unit test:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package de.gmorling.guiceonejb; import de.gmorling.guiceonejb.demo.WeatherCondition; import de.gmorling.guiceonejb.demo.WeatherService; public class WeatherServiceMockImpl implements WeatherService { public WeatherCondition getWeatherCondition(String locationCode) { return new WeatherCondition("Dresden", "Rainy", 5); } } |
To bind the WeatherService interface to the mock implementation instead of the real one, we have to write a Guice configuration module as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package de.gmorling.guiceonejb; import com.google.inject.AbstractModule; import de.gmorling.guiceonejb.demo.WeatherService; public class TestModule extends AbstractModule { public void configure() { bind(WeatherService.class).to(WeatherServiceMockImpl.class); } } |
That configuration module can now be used to create a new Guice injector, that we'll set into the GuiceInjectorHolder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
... @Test public void getWeatherInformationFromMock() throws Exception { GuiceInjectorHolder holder = (GuiceInjectorHolder) context.lookup("GuiceInjectorHolderBeanLocal"); assertNotNull(holder); holder.setInjector(Guice.createInjector(new TestModule())); WeatherInformationLocal bean = (WeatherInformationLocal) context.lookup("WeatherInformationBeanLocal"); assertNotNull(bean); WeatherCondition weatherCondition = bean.getWeatherCondition("GMXX0025"); assertEquals(new WeatherCondition("Dresden", "Rainy", 5), weatherCondition); } ... |
Guice will now inject the mock WeatherService implementation into the WeatherInformationBean, which makes the test independent from the real service at Yahoo!
Summary
That concludes the third and last part of my Guice tutorial. In this installment I showed, that is rather easy to use Guice within an EJB application (and that it is very simple to retrieve weather information from Yahoo! ;-)).
Guice is a great addition to EJB as it allows dependency injection and method interception also for objects not managed by the EJB container. The complete source code of this tutorial can be downloaded here. As usually I'd be happy on any feedback and comments.
16 comments:
One of the best articles I've seen on the java-blog-world for a long time. Taken from expreince, not keeping knowledge for yourself, very well written.
I will soon do a presentation on my local JUG about testing EJB. I am fan of http://misko.hevery.com, and it always was problem with EJB-DI since one needs to use new operators for the nonmanged objects. That resultet in code with higher test-cost. I was thinging about expreimenting with GUICE + EJB, but as always before sch experiments I used google to see if anywone else did that before. Google pointed your site :)
Hope you dont mind I will use your post in my presntaion keeping the refence.
Hi Paweł,
great to hear, that you like the post.
Feel free to take the post as input for your JUG presentation, just recommend the audience to visit my blog :-)
Where is your JUG, maybe you can publish the slides of your presentation on the web?
I'm reading Miskos blog, too. He's writing really good stuff.
Greets, Gunnar
sure thing, I will let everyone know about your blog so be aware of upcoming webtraffic from Wroclaw in Poland :)
I will let you know when the whole presentation is ready
do you use jabber maybe? if so catch me on blackrabbit@jabber.wroc.pl
gunnar: wouldn't it be better to create interceptor for PostConstruct lifecycle callback? This way guice will inject memebers only one (when the creation is of the bean is done, not when each bussines method is invoked). What do you thing?
Hi Paweł,
great - I am looking forward to you presentation.
I considered using the @PostConstruct callback, too. But I think this would be wrong for stateless session beans, if the injeced dependencies are stateful, as they would be shared between multiple requests to one bean.
It is another thing with stateful session beans, where exactly this behaviour might be wished.
For a beginning I decided to implement it conservative, re-injecting the dependencies with each call, but I am thinking about extending the @UsesGuice annotation with an attribute, that allows to specify, whether the dependencies should be injected only once or for each request.
Does that make sense?
Greets, Gunnar
"But I think this would be wrong for stateless session beans, if the injeced dependencies are stateful, as they would be shared between multiple requests to one bean."
I dont know.. I hardly can think of stateful dependencies in stateless bean in a real world scenario. Would that even make sens?
"but I am thinking about extending the @UsesGuice annotation with an attribute, that allows to specify, whether the dependencies should be injected only once or for each request"
hmm I guess it is a good solution. I mean we are giving the possibility for stateful dependencies in stateless bean if someone wish to have this sort of object graph.
The problem might still arise when u have one stateless session bean with two dependencies which of one is stateful and one is stateless. But in this scenario the staless dependency would have to live with the fact that it will be re-injected with each business method invocation. However it should not be a serious scalable problem, since this scenario would rarely happen in the real world.
Hey Gunnar!
Nice post, no question about it. Maybe you could privatize the injector in the GuiceInjectorHolderBean. From my point of view, there's no need for the package visibility.
Thx for that post!
@Anonymous: Thanks for pointing out that one. I've corrected it.
Gunnar I made some modifications:
two interceptors: AroundInvocationGuiceInterceptor and PostConstructGuiceInterceptor also @UsesGuice changed syntax accrodingly two your advise and looks like this:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UsesGuice {
InjectionPolicy policy() default InjectionPolicy.PostConstructPolicy;
}
so defult injection is postconstruct since it will be moste used case (stateful beans will use it and stateless beans as well (does hat do not have stateful dependencies)
for the stateless beans with stateful dependencies one cane use aroundinvocation policy
Hi Paweł,
that sounds good. What do you think about calling the attribute of the @UsesGuices annotation value() instead of policy()? That way one can use the shorter style when using the annotation:
@UsesGuice(InjectionPolicy.AroundInvokePolicy)
Why did you choose to use two interceptors instead of one? You could have a method annotated with @PostConstruct and one with @AroundInvoke within one class.
Greets, Gunnar
you may want to look at vtd-xml, the latest and most advanced xml processing api
vtd-xml
I wanted to have the accept-languages HTTP header (from a request to a servlet) available in stateless EJBs which are called by the servlet
(to e.g. set properties of returned objects to localized strings).
Because there is no request scope for EJBs I couldn't find a nice solution for that (e.g. set the language in a request scoped EJB and have this bean injected to stateless EJBs wherever the accept-language is needed).
My unsatisfying solution is to pass accept-language as parameter through each method in the call chain.
Guice supports request scope.
Do you think it is a good idea to use a request scoped (by Guice) object to hold the accept-language (initialized by the servlet) and than inject this object to statless EJBs wherever needed?
Anyway - very helpful post.
Günter, you could also store the language in a ThreadLocal variable and access it from there whenever needed. Just don't forget to clean up the ThreadLocal at the end of the request.
The solution you've exposed is very elegant, but what is to be done in the situation that i don't have a @Singleton bean at hand. The solution i'm working with is based on EJB3.0 standard (Websphere 7.0) and not EJB3.1, reason why i'd like to know if there is any workaround for having guice's injector being instantiated in another manner.
Extremely crisp and well written article. I borrowed this for my project. I am struggling in using this to inject into @Singleton bean annotated with @Startup. Any ideas on how to inject into these bean types? Thanks in advance.
our blog
our blog
Post a Comment