PrivateModule, TypeLiteral and AssistedInject - Google Guice techniques for complex scenarios
TweetPosted on Sunday Feb 07, 2016 at 11:44AM in Technology
In this entry I’ll introduce you some Google Guice techniques that can be used for some complex class designing scenarios, I’ve learned recently and found very helpful.
I’ve tested techniques that used in this entry works with Google Guice 3.0 and 4.0. example implementations and test cases can be obtained from my GitHub repository.
PrivateModule
Consider you have classes that something like following diagram:
You have a service class named MyService
which depends on the interface named MyStrategy
(DOC, Depended On Component). the DOC has two implementations, the one EnglishStrategy
says "Hello", when the method of it named sayHello()
is being called, and another one JapaneseStrategy
says "Konnichiwa".
There are annotation classes English
and Japanese
to distinguish two strategy MyService
depends on. these two annotations are written as follows:
import static java.lang.annotation.ElementType.*; @javax.inject.Qualifier @java.lang.annotation.Target({FIELD, PARAMETER, METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface English { }
On that situation, you want Guice to produce and inject two MyService
instances for your client class. you would declare injection points as follows:
// (1) call for injection of MyService annotated as @English @Inject @English MyService englishService; // (2) call for injection of MyService annotated as @Japanese @Inject @Japanese MyService japaneseService;
But it doesn’t work due to Guice failed to look MyService
binding annotated as @English
and @Japanese
up.
In such case, you can create two PrivateModule
and install them in your Module
implementation as follows:
@Override protected void configure() { install(new PrivateModule() { @Override protected void configure() { // bind MyService annotated as English to MyService PRIVATELY bind(MyService.class).annotatedWith(English.class).to(MyService.class); // expose MyService annotated as English GLOBALLY. this fulfills injection point (1) expose(MyService.class).annotatedWith(English.class); // bind MyStrategy to EnglishStrategy PRIVATELY bind(MyStrategy.class).to(EnglishStrategy.class); } }); install(new PrivateModule() { @Override protected void configure() { // bind MyService annotated as Japanese to MyService PRIVATELY bind(MyService.class).annotatedWith(Japanese.class).to(MyService.class); // expose MyService annotated as Japanese GLOBALLY. this fulfills injection point (2) expose(MyService.class).annotatedWith(Japanese.class); // bind MyStrategy to JapaneseStrategy PRIVATELY bind(MyStrategy.class).to(JapaneseStrategy.class); } }); }
These PrivateModule
create two bindings:
-
MyService
annotated withEnglish
orJapanese
. -
MyStrategy
without any annotation forEnglishStrategy
orJapaneseStrategy
privately
And, it exposes only the binding 1. If the binding 2 exposes as well or created in global scope, Guice will complain that there are two bindings for MyStrategy
without any annotation. that’s point what PrivateModule
helps.
TypeLiteral
Consider you have following injection points that with generics type parameter:
@Inject List<String> stringList; @Inject List<Integer> integerList;
To bind instances to these injection points, you need to use TypeLiteral
in your Module
implementation as follows:
@Override protected void configure() { bind(new TypeLiteral<List<String>>() { }).toInstance(new ArrayList<String>() {{ add("hello"); }}); bind(new TypeLiteral<List<Integer>>() { }).toInstance(new ArrayList<Integer>() {{ add(123); }}); }
With this module, Guice resolves correct the two injection points with use of generics type parameter.
Also you can leave constructing an instance to Guice if you have an implementation class. for example, consider you have a generic interface as follows:
public interface MyGenericService<T extends Number> { T get(); }
Injection points:
@Inject MyGenericService<Integer> integerService; @Inject MyGenericService<Double> doubleService;
Two implementations:
public class MyIntegerService implements MyGenericService<Integer> { @Override public Integer get() { return 123; } }
public class MyDoubleService implements MyGenericService<Double> { @Override public Double get() { return 0.5; } }
To create bindings to these injection points without creating instances in the module, you can write as follows in your module implementation:
bind(new TypeLiteral<MyGenericService<Integer>>(){}).to(MyIntegerService.class); bind(new TypeLiteral<MyGenericService<Double>>(){}).to(MyDoubleService.class);
AssistedInject
Consider you have classes that something like following diagram:
You have a service class named MyProduct
, which requires a parameter name
for its constructor. the parameter name
varies in each injection, so we create the factory interface MyFactory
for it. also, MyProduct
depends on MyCollaborator
.
You would create MyProduct
instance in your client as follows:
@Inject MyFactory factory; ... public void someMethodInClient() { final MyProduct product = factory.create("foo"); ... }
So, anyway, an implementation of MyFactory
is needed. it must be simple in this case but it brings another boilarplate code that should be avoided as possible. and imagine that if MyProduct
has a hundreds of DOCs? it will be longer as numbers of DOCs grows. manual construction procedure will be something like new MyProduct(new MyCollaborator1(), new MyCollaborator2(), …)
. obviously, it should be avoided.
With AssistedInject
, Guice will do it instead of you.
First, create MyFactory
as follows:
public interface MyFactory { MyProduct create(String name); }
Note that you don’t need to create an implementation class yourself, just forget about it. next, put an additional dependency to your pom.xml
:
<dependency> <groupId>com.google.inject.extensions</groupId> <artifactId>guice-assistedinject</artifactId> <version>4.0</version> </dependency>
Put following procedure to your Module
implementation:
@Override protected void configure() { install(new FactoryModuleBuilder().build(MyFactory.class)); }
Finally, declare an injection point in the MyProduct
class as follows. note that a constructor parameter name
is annotated as @Assisted
:
@Inject MyProduct(@Assisted final String name, final MyCollaborator collaborator) { this.name = name; this.collaborator = collaborator; }
With that, Guice automatically creates MyFactory
implementation for you, and handles parameter name
nicely.