Dependency injection with Guice

The servlet class is very simple as shown in the following:

package packt.java9.by.example.mastermind.servlet; 

import com.google.inject.Guice;
import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class Mastermind extends HttpServlet {
private static final Logger log = LoggerFactory.getLogger(Mastermind.class);

public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}

public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

Injector injector =
Guice.createInjector(new MastermindModule());
MastermindHandler handler =
injector.getInstance(MastermindHandler.class);
handler.handle(request, response);
}
}

Because many threads use servlets concurrently, and thus we cannot use instance fields holding data for a single hit, the servlet class does nothing else but create a new instance of a MastermindHandler class and invoke its handle method. Since there is a new instance of MastermindHandler for each request, it can store objects in fields specific to the request. To create a handler, we use the Guice library created by Google.

We have already talked about dependency injection. The handler needs a Table object to play, a ColorManager object to manage the colors, and a Guesser object to create a new guess, but creating these or fetching some prefabricated instances from somewhere is not the core functionality of the handler. The handler has to do one thing: handle the request; the instances needed to do this should be injected from outside. This is done by a Guice injector.

To use Guice, we have to list the library among the dependencies in build.gradle:

apply plugin: 'java' 
apply plugin: 'jetty'

repositories {
jcenter()
}

dependencies {
providedCompile "javax.servlet:javax.servlet-api:3.1.0"
testCompile 'junit:junit:4.12'
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'ch.qos.logback:logback-classic:1.0.11'
compile 'com.google.inject:guice:4.1.0'
}

jettyRun {
contextPath '/hello'
}

Then we have to create an injector instance that will do the injection. The injector is created with the following line in the servlet:

Injector injector = Guice.createInjector(new MastermindModule());

The instance of MastermindModule specifies what to inject where. This is essentially a configuration file in the Java format. Other dependency injector frameworks used, and use, XML and annotations to describe the injection binding and what to inject where, but Guice solely uses Java code. The following is the DI configuration code:

public class MastermindModule extends AbstractModule { 
@Override
protected void configure() {
bind(int.class)
.annotatedWith(Names.named("nrColors")).toInstance(6);
bind(int.class)
.annotatedWith(Names.named("nrColumns")).toInstance(4);
bind(ColorFactory.class).to(LetteredColorFactory.class);
bind(Guesser.class).to(UniqueGuesser.class);
}
}

The methods used in the configure method are created in a fluent API manner so that the methods can be chained one after the other and that the code can be read almost like English sentences. A good introduction to fluent API can be found at https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/. For example, the first configuration line could be read in English as

Bind to the class int wherever it is annotated with the @Name annotation having value "nrColor" to the instance 6.

(Note that the int value 6 is autoboxed to an Integer instance.)

The MastermindHandler class contains fields annotated with @Inject annotation:

@Inject 
@Named("nrColors")
private int NR_COLORS;
@Inject
@Named("nrColumns")
private int NR_COLUMNS;
@Inject
private HtmlTools html;
@Inject
Table table;
@Inject
ColorManager manager;
@Inject
Guesser guesser;

This annotation is not Guice-specific. @Inject is a part of the javax.inject package and is a standard part of JDK. JDK does not provide the dependency injector (DI) framework but supports the different frameworks so that they can use standard JDK annotations, and in case the DI framework is replaced, the annotations may remain the same and not framework-specific.

When the injector is called to create an instance of MastermindHandler, it looks at the class and sees that it has an int field annotated with @Inject and @Named("nrColors"), and finds in the configuration that such a field should have the value 6. It injects the value to the field before returning the MastermindHandler object. Similarly, it also injects the values into the other fields, and if it should create any of the objects to be injected, it does. If there are fields in these objects, then they are also created by injecting other objects and so on.

This way the DI framework removes the burden from the programmers' shoulder to create the instances. This is something fairly boring and is not the core feature of the classes anyway. Instead, it creates all the objects needed to have a functional MastermindHandler and links them together via the Java object references. This way, the dependencies of the different objects (MastermindHandler needs Guesser, ColorManager, and TableColorManager needs ColorFactory; and Table also needs ColorManager, and so on) become a declaration, specified using annotations on the fields. These declarations are inside the code of the classes, and it is the right place for them. Where else could we specify what the class needs to properly function than in the class itself?

The configuration in our example specifies that wherever there is a need for ColorFactory, we will use LetteredColorFactory, and that wherever we need Guesser, we will use UniqueGuesser. This is separated from the code and it has to be like that. If we want to change the guessing strategy, we replace the configuration and the code should work without modifying the classes that use the guesser.

Guice is clever enough and you need not specify that wherever there is a need for Table, we will use Table: there is no bind(Table.class).to(Table.class). First I created a line like that in the configuration, but Guice rewarded me with an error message, and now, writing it again in plain English, I feel really stupid. If I need a table I need a table. Really?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.198.174