Sunday, May 29, 2011

Using Guice-ified Jersey in Embedded Jetty

While IoC is a terrific way to promote software modularity, someone still has to write that bootstrap code to connect all the dots. Even though there is abundant information on how to use Guice, Jersey and Jetty respectively in a servlet environment, little is known about using all three together to programmatically configure servlets in Java. Here I will use an example to demonstrate how to write RESTful servlet using all three:
  1. Assemble framework libraries using Maven.
  2. Create a POJO interface and an implementation.
  3. Write a JAX-RS resoure to use Guice constructor injection and JAX-RS annotations.
  4. Write the Guice bootstrap code.
  5. Write the embedded Jetty start up code.
  6. Run and test.
First, assemble all necessary parts in a Maven pom.xml like this:
   
       7.4.1.v20110513
       1.7
       3.0
   

   
    
        org.eclipse.jetty
        jetty-servlet
        ${jetty.version}
    
 
     com.google.inject
     guice
     ${guice.verion}
 
 
     com.sun.jersey
     jersey-server
     ${jersey.version}
 
 
     com.sun.jersey.contribs
     jersey-guice
     ${jersey.version}
  
    
      junit
      junit
      ${junit.version}
      test
    
   
     
  
 
     maven2-repository.java.net
     Java.net Repository for Maven
     http://download.java.net/maven/2/
     default
  
  

We start with a simple POJO interface:
public interface GuicyInterface {
   String get();
}
And a simple implementation:
public class GuicyInterfaceImpl implements GuicyInterface {

   public String get() {
      return GuicyInterfaceImpl.class.getName();
   }
}
Now, write a JAX-RS resource to use both Guice and JAX-RS annotated injections:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import com.google.inject.Inject;@Path("/helloguice")

public class HelloGuice {
   private final GuicyInterface gi;
   
   @Inject
   public HelloGuice(final GuicyInterface gi) {
      this.gi = gi;
   }
   @GET
   @Produces("text/plain")
   public String get(@QueryParam("x") String x) {
      return "Howdy Guice. " + "Injected impl " + gi.toString() + ". Injected query parameter "+ (x != null ? "x = " + x : "x is not injected");
   }
}

Next, compose POJO bindins in a JerseyServletModule. This module will setup the Jersey-based JAX-RS framework for use with Guide injection. The GuiceServletContextListener is used to bootstrap Guice when the servet context is initialized.
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

public class HelloGuiceServletConfig extends GuiceServletContextListener {
   @Override
   protected Injector getInjector() {
      return Guice.createInjector(new JerseyServletModule() {
         @Override
         protected void configureServlets() {
            // Must configure at least one JAX-RS resource or the 
            // server will fail to start.
            bind(HelloGuice.class);
            bind(GuicyInterface.class).to(GuicyInterfaceImpl.class);
            
            // Route all requests through GuiceContainer
            serve("/*").with(GuiceContainer.class);
         }
      });
   }
}

Finally, write the main method using embedded Jersey to start Guice and Jersey together.
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;

import com.google.inject.servlet.GuiceFilter;

public class GuiceLauncher {
   public static void main(String[] args) throws Exception {
      // Create the server.
      Server server = new Server(8080);
      
      // Create a servlet context and add the jersey servlet.
      ServletContextHandler sch = new ServletContextHandler(server, "/");
      
      // Add our Guice listener that includes our bindings
      sch.addEventListener(new HelloGuiceServletConfig());
      
      // Then add GuiceFilter and configure the server to 
      // reroute all requests through this filter. 
      sch.addFilter(GuiceFilter.class, "/*", null);
      
      // Must add DefaultServlet for embedded Jetty. 
      // Failing to do this will cause 404 errors.
      // This is not needed if web.xml is used instead.
      sch.addServlet(DefaultServlet.class, "/");
      
      // Start the server
      server.start();
      server.join();
   }
}

If you run the main program on your local host, you can test the servlet using this URL:
http://localhost:8080/helloguice?x=q
Then, you should see a response like this:
Howdy Guice. Injected impl GuicyInterfaceImpl@3aaa3518. Injected query parameter x = q
Congratulations! You have now mastered the three most popular IoC frameworks for programming RESTful servlets!

14 comments:

  1. Isn't there any web.xml work that goes along with this?

    ReplyDelete
  2. No. This is pure Jetty embedding which eschrews web.xml entirely. If you prefer a standard servlet container with web.xml, then swap GuiceLauncher with the exact web.xml fragment as shown here:
    http://code.google.com/p/google-guice/wiki/Servlets

    ReplyDelete
  3. We found ourselves writing this kind of code over and over, so we extracted it into a small library.

    http://github.com/talis/jersey-common

    ReplyDelete
  4. Any idea how to use the Jersey UriConnegFilter with this stack? My goal is doing context negotiation based on url extension without needing to build something, hence me trying to use this servlet. All the documentation I've found only explains how to use it when you're using a jersey servlet. I tried to chain this guy to the juice filter, but it's not even actually a filter, despite it's name - doesn't extend javax.servlet.Filter.

    ReplyDelete
  5. See http://jersey.java.net/nonav/apidocs/1.11/contribs/jersey-guice/com/sun/jersey/guice/spi/container/servlet/package-summary.html for an overview of what goes into web.xml

    ReplyDelete
  6. So useful, thank you very much!

    ReplyDelete
  7. Thanks for this guide!

    I was using restlet before and this solution is much more simple an clean than any restlet guice integration I could find.

    ReplyDelete
  8. This post is from 2011, is it still valid?

    ReplyDelete
  9. This has been a great help for me. I have a question: how would you change this setup to serve static resources from some location, or is it already set to do that, and if so, which location?

    Thanks again

    ReplyDelete
  10. Hey have you tried this to deploy on a JBoss 7. Getting a "Root-Resource not found"-Exception here.

    ReplyDelete
  11. One thing I don't understand here, and it's kind of deal breaker for me:
    // Route all requests through GuiceContainer
    serve("/*").with(GuiceContainer.class);

    Why?
    I mean Jersey can serve Resources out-of-the-box just fine, why do I have to route everything through guice? I actually only want guice to set @Inject dependencies, but not the Resources itself.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Guice has to inject the Resource so that any injected resources that are required by each @Path (resource you have configured for the endpoint) has Guice IoC injection when created. This is typical "Guice" the originating class has to be injected, in order to inject children.
      I suppose there is nothing stopping you from creating an Injection and manually injecting a "root" level object when you need something handled by Guice IoC (not very elegant though).

      Delete