Guice Servlets Integration

admin  

Using Google Guice in web applications can raise some issues. First of all when we talk about web applications we talk about servlets and we talk about managed environments. The creation of servlets in not controlled by us, when we write the application, it is controlled by the web application container. This generates some problems:

  • Because we can not control the servlet creation we can not use Guice to inject servlet members in the classic way.
  • Bootstrapping: we need a mechanism to bootstrap Guice into the application. The best way is to do it at start up before any http request will be invoked.

The key element to bootstrap Google Guice to a java servlet based application is ServletContext. For each web application there is only one ServletContext instance per JVM. ServletContext is used to manage data which is accessible to all the servlets, and it can be used by the servlets to share data between them. Starting with Servlet 2.3 specification Sun provides a listener mechanism which can be used to instantiate objects at the application start up, outside of any servlet scope in the ServletContext.

This techniques can not be used in distributed environments. If we use it we'll have one injector instance on each JVM because on distributed environments for each JVM there is one ServletContext. ServletContext on one JVM is not visible from another JVM, so if we can not use guice scopes distributed on multiple machines.

Initializing Guice Injector in ServletContext

As we said there is only one instance of javax.servlet.ServletContext(as long as we don't have a distributed application). We can trigger the initialization of the ServletContext using ServletContextListener. All we have to do is to implement the ServletContextListener.contextInitialized to create the google guice injector, and to set it as an attribute in the ServletContext:

package com.exam.web.guice;

import java.lang.reflect.InvocationTargetException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;

public class GuiceServletContextListener implements ServletContextListener { 
    public static final String KEY = Injector.class.getName(); 
 
    public void contextInitialized(ServletContextEvent servletContextEvent)
    { 
        servletContextEvent.getServletContext()
            .setAttribute(KEY, getInjector(servletContextEvent.getServletContext())); 
    } 
 
    public void contextDestroyed(ServletContextEvent servletContextEvent) { 
        servletContextEvent.getServletContext().removeAttribute(KEY); 
    } 
 
    @SuppressWarnings("unchecked") 
    private Injector getInjector(ServletContext servletContext) { 
        String className = servletContext.getInitParameter("module"); 
        try { 
            Class module = (Class

) Class.forName(className); 
            return Guice.createInjector(module.getConstructor().newInstance()); 
        } 
        catch (ClassNotFoundException e) { throw new RuntimeException(e); }
        catch (NoSuchMethodException e) { throw new RuntimeException(e); }
        catch (InvocationTargetException e) { throw new RuntimeException(e); }
        catch (IllegalAccessException e) { throw new RuntimeException(e); }
        catch (InstantiationException e) { throw new RuntimeException(e); }
    } 
}

Then we have to change the web.xml to define the listener to be used by the web application container:

<listener>
        <listener-class>
            com.exam.web.guice.GuiceServletContextListener
        </listener-class>
    </listener>

Storing module configuration in web.xml

We use web.xml to define which module to be used by Guice in block. When we create the guice injector the parameter is read and used:

String className = servletContext.getInitParameter("module");

The class name is defined like this in web.xml:

<context-param> 
        


module</param-name> 
        


com.webapplication.bus.HibernateDaoModule</param-value> 
        <description>Guice Module to be used for the servlets app</description> 
    </context-param>

Finally in the servlet we can use this expression to get the injector:
(Injector) servletContext.getAttribute(GuiceServletContextListener.KEY)

We can add an injector field to the servlet and "inject" the injector into it when the servlet is initialized(a super servlet class can be created to define this code in only one place):

public class MyServlet  extends HttpServlet {
    
    Injector injector;
        ...
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        
        HibernateUtil.Configure(true);  
        
        ServletContext servletContext = config.getServletContext(); 
        injector = (Injector) servletContext.getAttribute(GuiceServletContextListener.KEY); 
        injector.injectMembers(this);
          
        managersRegistry = injector.getInstance(ManagersRegistry.class);
      		      
        super.init(config);
    }	
}