Kohei Nozaki's blog 

PrivateModule, TypeLiteral and AssistedInject - Google Guice techniques for complex scenarios


Posted 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:

237fbbbb c5d0 4315 aeef ccc9e8b2605e

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:

  1. MyService annotated with English or Japanese.

  2. MyStrategy without any annotation for EnglishStrategy or JapaneseStrategy 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:

703fa153 c547 4dd9 9145 70f36d55988e

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.



Comments:

Thank you for taking the time to put together so much useful information!
I have one little question about the last part, where you're explaining the use of @AssistedInject. How is an instance of MyCollaborator collaborator resolved for the MyProduct constructor? Shouldn't there be a different constructor which only requires the String name argument, say @Injected MyProduct(@Assisted final String name) for this example to work?

Posted by Filip on July 28, 2016 at 02:18 PM JST #


Hi Filip,

I'm not sure if I understand your concern, but the example works as is. you can get the whole project from https://github.com/lbtc-xxx/guice-examples and just run "mvn test" to see it in action.

Note that the diagram states MyProduct has the constructor which takes a String, but in the real code, the constructor takes a String and a MyCollaborator.

Posted by Kohei on July 28, 2016 at 10:44 PM JST #


+1 for taking the time to answer this one! Your note got me to read your article more carefully and realized that I've totally missed your explanation of using @Assisted, namely that no longer does one need to provide explicit constructors and just trust guice to inject constructor args.

Posted by Filip on August 01, 2016 at 05:52 PM JST #


Leave a Comment

HTML Syntax: NOT allowed