by Jean Tessier
This document shows how to pass an object between Guice providers so each one can initialize it in turn.
You can download the finalized code from here.
All example were tested with JUnit 4.5 and jMock 2.2.4 using JDK 1.5.0_16 and the Guice snapshot dated 2008-11-23.
I got the idea for this pattern as I was trying to reuse a framework. This framework had been written for one specific application but its authors felt it was generic enough to write other applications. I was to be the first one to try and use it outside its original application.
The framework uses Guice for its dependency injection. Early on, I encountered
a problem where the original application did a lot of custom setting up in a
provider method of its Module
. I needed to have a similar module that would
duplicate a lot of the setup, minus the bits specific to the original
application, plus some new bits specific to my application. I wanted to
extract the common parts to one generic module and trick Guice into chaining
providers on the same object to complete its initialization.
This is their story.
Imagine the following service interface:
package service;
public interface Service { void setupClient1(); void setupClient2(); String getState(); }
It has different setup methods for different clients. This example is to illustrate that different clients my setup the service differently. In real life, there would be no dependency from the service upon its clients. For a more concrete example, the service might be a web server class and each client might configure a different set of URLs.
For the purposes of this discussion, here is a quick specification of what we are looking for:
package service;
import org.junit.*; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*;
public class TestServiceImpl { private Service sut;
@Before public void setUp() { sut = new ServiceImpl(); }
@Test public void testDefault_StateEqualsGeneric() { assertThat(sut.getState(), is(equalTo("generic"))); }
@Test public void testSetupClient1_StateEqualsClient1() { sut.setupClient1(); assertThat(sut.getState(), is(equalTo("client1"))); }
@Test public void testSetupClient2_StateEqualsClient2() { sut.setupClient2(); assertThat(sut.getState(), is(equalTo("client2"))); } }
And here is a simple implementation that satisfied it:
package service;
public class ServiceImpl implements Service { private String state = "generic";
public void setupClient1() { state = "client1"; }
public void setupClient2() { state = "client2"; }
public String getState() { return state; } }
To begin with, we need a base Module
that creates a generic Service
implementation. Here is a test for it:
package generic;
import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import org.junit.*; import service.*;
public class TestGenericModule { private GenericModule sut;
@Before public void setUp() { sut = new GenericModule(); }
@Test public void testConfigure() { sut.configure(); }
@Test public void testProvideService() { Service actualService = sut.provideService(); assertThat(actualService, is(notNullValue())); assertThat(actualService.getState(), is(equalTo("generic"))); } }
And here is a generic Guice module with a provider method to create a plain
ServiceImpl
whenever a Service
instance is required.
package generic;
import com.google.inject.*; import service.*;
public class GenericModule extends AbstractModule { protected void configure() { // Do nothing. }
@Provides public Service provideService() { return new ServiceImpl(); } }
You can use it like this:
Injector injector = Guice.createInjector(new GenericModule()); Service service = injector.getInstance(Service.class);
The variable service
now references a plain ServiceImpl
.
Different clients who require a customized Service
instance need to use a
their own module instead of the previous GenericModule
.
A hypothetical client No. 1 could need a module that follows the following
specification, that it calls setupClient1()
on an existing Service
instance:
package client1;
import static org.hamcrest.Matchers.*; import org.jmock.*; import org.jmock.integration.junit4.*; import static org.junit.Assert.*; import org.junit.*; import org.junit.runner.*; import service.*;
@RunWith(JMock.class) public class TestClient1Module { private Mockery context = new Mockery();
private Client1Module sut;
@Before public void setUp() { sut = new Client1Module(); }
@Test public void testConfigure() { sut.configure(); }
@Test public void testProvideService() { final Service mockService = context.mock(Service.class); context.checking(new Expectations() {{ one (mockService).setupClient1(); }}); Service actualService = sut.provideService(mockService); assertThat(actualService, is(sameInstance(mockService))); } }
This hypothetical client No. 1 could use this module:
package client1;
import com.google.inject.*; import service.*;
public class Client1Module extends AbstractModule { protected void configure() { // Do nothing. }
@Provides public Service provideService() { Service service = new ServiceImpl(); service.setupClient1(); return service; } }
A hypothetical client No. 2 could require this specification instead:
package client2;
import static org.hamcrest.Matchers.*; import org.jmock.*; import org.jmock.integration.junit4.*; import static org.junit.Assert.*; import org.junit.*; import org.junit.runner.*; import service.*;
@RunWith(JMock.class) public class TestClient2Module { private Mockery context = new Mockery();
private Client2Module sut;
@Before public void setUp() { sut = new Client2Module(); }
@Test public void testConfigure() { sut.configure(); }
@Test public void testProvideService() { final Service mockService = context.mock(Service.class); context.checking(new Expectations() {{ one (mockService).setupClient2(); }}); Service actualService = sut.provideService(mockService); assertThat(actualService, is(sameInstance(mockService))); } }
Which this implementation satisfies:
package client2;
import com.google.inject.*; import service.*;
public class Client2Module extends AbstractModule { protected void configure() { // Do nothing. }
@Provides public Service provideService() { Service service = new ServiceImpl(); service.setupClient2(); return service; } }
These two modules have some problems.
ServiceImpl
.ServiceImpl
implementation of Service
.
It would be better if we could somehow inject a Service
implementation and
have the module do its custom configuration.
Ideally, I would like to separate the choice of Service
implementation, as
provided by GenericModule
, from the application-specific configuration, as
provided by Client1Module
and Client2Module
.
I could rewrite Client1Module
as:
package client1;
import com.google.inject.*; import service.*;
public class Client1Module extends AbstractModule { protected void configure() { // Do nothing. }
@Provides public Service provideService(Service service) { service.setupClient1(); return service; } }
And use it like this:
Injector injector = Guice.createInjector(new GenericModule(), new Client1Module()); Service service = injector.getInstance(Service.class);
I would hope that Guice picks to call Client1Module.provideService()
to
satisfy my request and injects a Service
into this call by calling
GenericModule.provideService()
. But that's not the case. Because I now have
two providers for Service
, Guice gets confused and throws an error with the
following message:
A binding to service.Service was already configured at generic.GenericModule.provideService().
I need to differentiate between the two providers, somehow.
I can use annotations to distinguish between the kinds of Service
each
providers is responsible for.
First, I qualify the Service
provided by GenericModule
as "generic".
package generic;
import com.google.inject.*; import com.google.inject.name.*; import service.*;
public class GenericModule extends AbstractModule { protected void configure() { // Do nothing. }
@Provides @Named("generic") public Service provideService() { return new ServiceImpl(); } }
I will also qualify the Service
provided by client No. 1's module. This way,
it is explicit in the calling code which Service
I am interested in.
package client1;
import com.google.inject.*; import com.google.inject.name.*; import service.*;
public class Client1Module extends AbstractModule { protected void configure() { // Do nothing. }
@Provides @Named("client1") public Service provideService(@Named("generic") Service service) { service.setupClient1(); return service; } }
Here, it is made explicit that client No. 1 requires a "client1" flavor of
Service
, which will be provided by Client1Module.provideService()
. The
latter requires a "generic" flavor of Service
, which will be provided by
GenericModule.provideService()
. The client simply asks for what it needs and
lets Guice take care of the rest.
Injector injector = Guice.createInjector(new GenericModule(), new Client1Module()); Service service = injector.getInstance(Key.get(Service.class, Names.named("client1")));
Client No. 2 would use the following module to obtain a "client2" flavor of
Service
.
package client2;
import com.google.inject.*; import com.google.inject.name.*; import service.*;
public class Client2Module extends AbstractModule { protected void configure() { // Do nothing. }
@Provides @Named("client2") public Service provideService(@Named("generic") Service service) { service.setupClient2(); return service; } }
And use it like this:
Injector injector = Guice.createInjector(new GenericModule(), new Client2Module()); Service service = injector.getInstance(Key.get(Service.class, Names.named("client2")));
More realistically, I would declare a Client1
class that requires a "client1"
kind of Service
:
package client1;
import com.google.inject.*; import com.google.inject.name.*; import generic.*; import service.*;
public class Client1 extends Client { @Inject public Client1(@Named("client1") Service service) { super(service); } }
And a Client2
class that requires a "client2" kind of Service
:
package client2;
import com.google.inject.*; import com.google.inject.name.*; import generic.*; import service.*;
public class Client2 extends Client { @Inject public Client2(@Named("client2") Service service) { super(service); } }
Here is a simple, generic Client
superclass:
package generic;
import service.*;
public class Client { private final Service service;
public Client(Service service) { this.service = service; }
public Service getService() { return service; } }
The nice thing is that Client
does not need to know about the difference
between clients No. 1 and No. 2. It deals exclusively in terms of the generic
Service
interface. Guice uses the configuration information to pick the
right implementation and customize it appropriately.
Injector injector = Guice.createInjector(new GenericModule(), new Client1Module()); Client client = injector.getInstance(Client1.class);
This pattern for using Guice might prove useful.
Another possibility is that the way Serice
gets customized is all wrong. I
could conceive of a Customizer
object that would get injected into
GenericModule.provideService()
. Client1Module
and Client2Module
would
provide bindings to specific types of Customizer
, and that would be the end
of it. This design might be more robust.
But if you cannot change GenericModule
, then maybe the pattern I just covered
can be of assistance.
After I sent this document to a few people for review, someone brought up the
upcoming @New
annotation for Guice.
It seems to address some of the same concerns as the pattern I describe on this
page. One difference is that my pattern can be chained for as long as
necessary, whereas you can use the @New
annotation only once.
This document was first written on 2009-01-28. It was last updated on 2009-01-29.