Kohei Nozaki's blog 

Entries tagged [di]

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.