Google Guice

0
123
13 min read

(For more resources related to this topic, see here.)

Structure of flightsweb application

Our application has two servlets: IndexServlet, which is a trivial example of forwarding any request, mapped with “/” to index.jsp and FlightServlet, which processes the request using the functionality we developed in the previous section and forwards the response to response.jsp. Here in, we simply declare the FlightEngine and SearchRequest as the class attributes and annotate them with @Inject. FlightSearchFilter is a filter with the only responsibility of validating the request parameters. Index.jsp is the landing page of this application and presents the user with a form to search the flights, and response.jsp is the results page.

The flight search form will look as shown in the following screenshot:

The search page would subsequently lead to the following result page.

In order to build the application, we need to execute the following command in the directory, where the pom.xml file for the project resides:

shell> mvn clean package

The project for this article being a web application project compiles and assembles a WAR file, flightsweb.war in the target directory. We could deploy this file to TOMCAT.

Using GuiceFilter

Let’s start with a typical web application development scenario. We need to write a JSP to render a form for searching flights and subsequently a response JSP page. The search form would post the request parameters to a processing servlet, which processes the parameters and renders the response.

Let’s have a look at web.xml. A web.xml file for an application intending to use Guice for dependency injection needs to apply the following filter:

<filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

It simply says that all the requests need to pass via the Guice filter. This is essential since we need to use various servlet scopes in our application as well as to dispatch various requests to injectable filters and servlets. Rest any other servlet, filter-related declaration could be done programmatically using Guice-provided APIs.

Rolling out our ServletContextListener interface

Let’s move on to another important piece, a servlet context listener for our application. Why do we need a servlet context listener in the first place? A servlet context listener comes into picture once the application is deployed. This event is the best time when we could bind and inject our dependencies.

Guice provides an abstract class, which implements ServletContextListener interface. This class basically takes care of initializing the injector once the application is deployed, and destroying it once it is undeployed. Here, we add to the functionality by providing our own configuration for the injector and leave the initialization and destruction part to super class provided by Guice. For accomplishing this, we need to implement the following API in our sub class:

protected abstract Injector getInjector();

Let’s have a look at how the implementation would look like:

package org.packt.web.listener; import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; public class FlightServletContextListener extends GuiceServletContextListener { @Override protected Injector getInjector() { return Guice.createInjector( new ServletModule(){ @Override protected void configureServlets() { // overridden method contains various // configurations } }); }}

Here, we are returning the instance of injector using the API:

public static Injector createInjector(Module... modules)

Next, we need to provide a declaration of our custom FlightServletContextListener interface in web.xml:

<listener> <listener-class> org.packt.web.listener.FlightServletContextListener </listener-class> </listener>

ServletModule – the entry point for configurations

In the argument for modules, we provide a reference of an anonymous class, which extends the class ServletModule. A ServletModule class configures the servlets and filters programmatically, which is actually a replacement of declaring the servlet and filters and their corresponding mappings in web.xml.

Why do we need to have a replacement of web.xml in the first place? Think of it on different terms. We need to provide a singleton scope to our servlet. We need to use various web scopes like RequestScope, SessionScope, and so on for our classes, such as SearchRequest and SearchResponse. These could not be done simply via declarations in web.xml. A programmatic configuration is far more logical choice for this. Let’s have a look at a few configurations we write in our anonymous class extending the ServletModule:

new ServletModule(){ @Override protected void configureServlets() { install(new MainModule()); serve("/response").with(FlightServlet.class); serve("/").with(IndexServlet.class); } }

A servlet module at first provides a way to install our modules using the install() API. Here, we install MainModule, which is reused from the previous section. Rest all other modules are installed from MainModule.

Binding language

ServletModule presents APIs, which could be used for configuring filters and servlets. Using these expressive APIs known as EDSL, we could configure the mappings between servlets, filters, and respective URLs.

Guice uses an embedded domain specific language or EDSL to help us create bindings simply and readably. We are already using this notation while creating various sort of bindings using the bind() APIs. Readers could refer to the Binder javadoc, where EDSL is discussed with several examples.

Mapping servlets

Here, following statement maps the /response path in the application to the FlightServlet class’s instance:

serve("/response").with(FlightServlet.class);

serve() returns an instance of ServletKeyBindingBuilder. It provides various APIs, using which we could map a URL to an instance of servlet. This API also has a variable argument, which helps to avoid repetition. For example, in order to map /response as well as /response-quick, both the URLs to FlightServlet.class we could use the following statement:

serve("/response","/response-quick").with(FlightServlet.class);

serveRegex() is similar to serve(), but accepts the regular expression for URL patterns, rather than concrete URLs. For instance, an easier way to map both of the preceding URL patterns would be using this API:

serveRegex("^response").with(FlightServlet.class);

ServletKeyBindingBuilder.with() is an overloaded API. Let’s have a look at the various signatures.

void with(Class<? extends HttpServlet> servletKey); void with(Key<? extends HttpServlet> servletKey);

To use the key binding option, we will develop a custom annotation @FlightServe. FlightServlet will be then annotated with it. Following binding maps a URL pattern to a key:

serve("/response").with(Key.get(HttpServlet.class, FlightServe.class));

Since this, we need to just declare a binding between @FlightServe and FlightServlet, which will go in modules:

bind(HttpServlet.class). annotatedWith(FlightServe.class).to(FlightServlet.class)

What is the advantage of binding indirectly using a key? First of all, it is the only way using which we could separate an interface from an implementation. Also it helps us to assign scope as a part of the configuration. A servlet or a filter must be at least in singleton In this case we can assign scope directly in configuration. The option of annotating a filter or a servlet with @Singleton is also available, although.

Guice 3.0 provides following overloaded versions, which even facilitate providing initialization parameters and hence provide type safety.

void with(HttpServlet servlet); void with(Class<? extends HttpServlet> servletKey, Map<String, String>
initParams); void with(Key<? extends HttpServlet> servletKey, Map<String, String>
initParams); void with(HttpServlet servlet, Map<String, String> initParams);

An important point to be noted here is that ServletModule not only provides a programmatic API to configure the servlets, but also a type-safe idiomatic API to configure the initialization parameters. It is not possible to ensure type safety while declaring the initialization parameters in web.xml.

Mapping filters

Similar to the servlets, filters could be mapped to URL patterns or regular expressions. Here, the filter() API is used to map a URL pattern to a Filter. For example:

filter("/response").through(FlightSearchFilter.class);

filter() returns an instance of FilterKeyBindingBuilder. FlightKeyBindingBuilder provides various APIs, using which we can map a URL to an instance of filter. filter() and filterRegex() APIs take exactly the same kind of arguments as serve() and serveRegex() does when it comes to handling the pure URLs or regular expressions.

Let’s have a look at FilterKeyBindingBuilder.through() APIs. Similar to ServletKeyBindingBuilder.with() it also provides various overloaded versions:

void through(Class<? extends Filter> filterKey); void through(Key<? extends Filter> filterKey);

Key mapped to a URL, which is then bound via annotation to an implementation could be exemplified as:

filter("/response"). through(Key.get(Filter.class,FlightFilter.class));

The binding is done through annotation. Also note, that the filter implementation is deemed as singleton in scope.

bind(Filter.class). annotatedWith(FlightFilter.class). to(FlightSearchFilter.class).in(Singleton.class);

Guice 3.0 provides following overloaded versions, which even facilitate providing initialization parameters and provide type safety:

void through(Filter filter); void through(Class<? extends Filter> filterKey, Map<String, String>
initParams); void through(Key<? extends Filter> filterKey, Map<String, String>
initParams); void through(Filter filter, Map<String, String> initParams);

Again, these type safe APIs provide a better configuration option then declaration driven web.xml.

Web scopes

Aside from dependency injection and configuration facilities via programmable APIs, Guice provides feature of scoping various classes, depending on their role in the business logic.

As we saw, while developing the custom scope, a scope comes into picture during binding phase. Later, when the scope API is invoked, it brings the provider into picture. Actually it is the provider which is the key to the complete implementation of the scope. Same thing applies for the web scope.

@RequestScoped

Whenever we annotate any class with either of servlet scopes like @RequestScoped or @SessionScoped, call to scope API of these respective APIs are made. This results in eager preparation of the Provider<T> instances. So to harness these providers, we need not configure any type of binding, as these are implicit bindings. We just need to inject these providers where we need the instances of respective types.

Let us discuss various examples related to these servlet scopes. Classes scoped to @RequestScoped are instantiated on every request. A typical example would be to instantiate SearchRequest on every request. We need to annotate the SearchRQ with the @RequestScoped.

@RequestScoped public class SearchRequest { ……}

Next, in FlightServlet we need to inject the implicit provider:

@Inject private Provider<SearchRequest> searchRQProvider;

The instance could be fetched simply by invoking the .get() API of the provider:

SearchRequest searchRequest = searchRQProvider.get();

@SessionScoped

The same case goes with @SessionScoped annotation. In FlightSearchFilter, we need an instance of RequestCounter (a class for keeping track of number of requests in a session). This class RequestCounter needs to be annotated with @SessionScoped, and would be fetched in the same way as the SearchRequest. However the Provider takes care to instantiate it on every new session creation:

@SessionScoped public class RequestCounter implements Serializable{……}

Next, in FlightSearchFilter, we need to inject the implicit provider:

@Inject private Provider<RequestCounter> sessionCountProvider;

The instance could be fetched simply by invoking the .get() API of the provider.

@RequestParameters

Guice also provides a @RequestParameters annotation. It could be directly used to inject the request parameters. Let’s have a look at an example in FlightSearchFilter. Here, we inject the provider for type Map<String,String[]> in a field:

@Inject @RequestParameters private Provider<Map<String, String[]>> reqParamMapProvider;

As the provider is bound internally via InternalServletModule (Guice installs this module internally), we can harness the implicit binding and inject the Provider.

An important point to be noted over here is that, in case we try to inject the classes annotated with ServletScopes, like @RequestScoped or @SessionScoped, outside of the ServletContext or via a non HTTP request like RPC, Guice throws the following exception:

SEVERE: Exception starting filter guiceFilter com.google.inject.ProvisionException: Guice provision errors: Error in custom provider, com.google.inject.OutOfScopeException: Cannot
access scoped object. Either we are not currently inside
an HTTP Servlet request, or you may have forgotten to apply
com.google.inject.servlet.GuiceFilter as a servlet filter for this
request.

This happens because the Providers associated with these scopes necessarily work with a ServletContext and hence it could not complete the dependency injection. We need to make sure that our dependencies annotated with ServletScopes come into the picture only when we are in WebScope.

Another way in which the scoped dependencies could be made available is by using the injector.getInstance() API. This however requires that we need to inject the injector itself using the @Inject injector in the dependent class. This is however not advisable as it is mixing dependency injection logic with the application logic. We need to avoid this approach.

Exercising caution while scoping

Our examples illustrate cases where we are injecting the dependencies with narrower scope in the dependencies of wider scope. For example, RequestCounter (which is @SessionScoped) is injected in FlightSearchFilter (which is a singleton).

This needs to be very carefully designed, as in when we are absolutely sure that a narrowly scoped dependency should be always present else it would create a problem. It basically results in scope widening, which means that apparently we are widening the scope of SessionScoped objects to that of singleton scoped object, the servlet. If not managed properly, it could result into memory leaks, as the garbage collector could not collect the references to the narrowly scoped objects, which are held in the widely scoped objects.

Sometimes this is unavoidable, in such a case we need to make sure we are following two basic rules:

  • Injecting the narrow scoped dependency using Providers. By following this strategy, we never allow the widely scoped class to hold the reference to the narrowly scoped dependency, once it goes out of scope. Do not get the injector instance injected in the wide scoped class instance to fetch the narrow scoped dependency, directly. It could result in hard to debug bugs.
  • Make sure that we use the dependent narrowly scoped objects in APIs only. This lets these to live as stack variables rather than heap variables. Once method execution finishes, the stack variables are garbage collected. Assigning the object fetched from the provider to a class level reference could affect garbage collection adversely, and result in memory leaks.

Here, we are using these narrowly scoped dependencies in APIs: doGet() and doFilter(). This makes sure that they are always available.

Contrarily, injecting widely scoped dependencies in narrowly scoped dependencies works well, for example, in a @RequestScoped annotated class if we inject a @SessionScoped annotated dependency, it is much better since it is always guaranteed that dependency would be available for injection and once narrowly scoped object goes out of scope it is garbage collected properly.

We retrofitted our flight search application in a web environment. In doing so we learned about many aspects of the integration facilities Guice offers us:

  • We learned how to set up the application to use dependency injection using GuiceFilter and a custom ServletContextListener.
  • We saw how to avoid servlet, filter mapping in web.xml, and follow a safer programmatic approach using ServletModule.
  • We saw the usage of various mapping APIs for the same and also certain newly introduced features in Guice 3.0.
  • We discussed how to use the various web scopes.

LEAVE A REPLY

Please enter your comment!
Please enter your name here