RESTEasy, embedded Jetty, Swagger and Guice

In the previous post, I wrote about the steps I followed when adding support for Swagger and Swagger UI to a RESTful API built using RESTEasy.

In this post, I'll walk you through the steps I followed when adding support for Guice (a lightweight dependency injection framework that implements JSR-330) to a RESTful API built using RESTEasy (packaged in a Fat JAR with an embedded Jetty instance and no web.xml).

Prerequisites

  • OpenJDK for Java 1.8
  • Git
  • Maven (I'm using 3.3.9)
  • The Eclipse IDE for Java EE Developers (I'm using Neon)

Note: This post will walk you through the steps required to install the OpenJDK. And, this post will walk you through the steps required to install Git, Maven and the Eclipse IDE.

Getting Started

You've probably heard the expression:

highly cohesive, loosely coupled

When building microservices, dependency injection is one of the tools we can use to help us achieve loose coupling. The most commonly used form of dependency injection is constructor injection and to quote Guice's getting started guide:

To construct an object, you first build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph.

The POM.XML

The first thing we need to do is to update the list of library dependencies in our POM's dependencies element:

<dependencies>

  ...
  
  <!-- RESTEasy runtime dependencies -->
  ...
  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-guice</artifactId>
    <version>${resteasy.version}</version>
  </dependency>
  
  <!-- Guice runtime dependencies -->
  <dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>${guice.version}</version>
  </dependency>
  <dependency>
    <groupId>com.google.inject.extensions</groupId>
    <artifactId>guice-servlet</artifactId>
    <version>${guice.version}</version>
  </dependency>
  <dependency>
    <groupId>com.google.inject.extensions</groupId>
    <artifactId>guice-multibindings</artifactId>
    <version>${guice.version}</version>
  </dependency>      
  ...
    
</dependencies>

We need to include the required Guice libraries as well as the RESTEasy Guice library.

Bindings

Guice includes servlet extensions that allow us to take advantage of type-safe configuration for our servlet and filter components.

To construct an object Guice needs know what to build, how to resolve dependencies, and how to wire everything together. To specify how dependencies are resolved, we create bindings for our servlets and filters.

We do this by extending Guice's ServletModule class and overriding its configureServlets() method. Let's take a look at the JettyModule:

package org.robferguson.resteasy.examples.fatjar.jetty;

import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.ServletModule;

public class JettyModule extends ServletModule {

  @Override
  protected void configureServlets() {

    bind(GuiceFilter.class);
  }
}

The JettyModule extends the ServletModule class and overrides its configureServlets() method. In the configureServlets() method we bind Guice’s GuiceFilter class.

In order for Guice to inject dependencies we must route all requests through the GuiceFilter. This filter listens for all requests and if it finds an @Inject annotation it will deliver the appropriate dependencies to that object.

Note: In the Main classes run() method (see below) we will register the Guice Filter with the application's ServletContextHandler instance.

We also need to create bindings for RESTEasy:

...

public class RestEasyModule extends ServletModule {

  private final String path;

  public RestEasyModule() {
    this.path = null;
  }

  public RestEasyModule(final String path) {
    this.path = path;  // e.g., "/api"
  }

  @Override
  protected void configureServlets() {

    bind(GuiceResteasyBootstrapServletContextListener.class);
    bind(HttpServletDispatcher.class).in(Singleton.class);

    if (path == null) {
      serve("/*").with(HttpServletDispatcher.class);
    } else {
      final Map<String, String> initParams =
        ImmutableMap.of("resteasy.servlet.mapping.prefix", path);
      serve(path + "/*").with(HttpServletDispatcher.class, initParams);
    }
  }
}

In the RestEasyModule’s configureServlets() method we bind the RESTEasy framework’s GuiceResteasyBootstrapServletContextListener and HttpServletDispatcher classes.

GuiceResteasyBootstrapServletContextListener is a helper class that sets up all of RESTEasy's (Guice required) bindings.

And, Swagger:

...

public class SwaggerModule extends ServletModule {

  private final String path;

  public SwaggerModule() {
    this.path = null;
  }

  public SwaggerModule(final String path) {
    this.path = path;  // e.g., "/api"
  }

  @Override
  protected void configureServlets() {

    Multibinder<ServletContextListener> multibinder = 
      Multibinder.newSetBinder(binder(), ServletContextListener.class);
    multibinder.addBinding().to(SwaggerServletContextListener.class);

    bind(ApiListingResource.class);
    bind(SwaggerSerializers.class);

    if (path == null) {
      filter("/*").through(ApiOriginFilter.class);
    } else {
      filter(path + "/*").through(ApiOriginFilter.class);
    }
  }
}

In the SwaggerModule’s configureServlets() method we bind the Swagger framework’s ApiListingResource and SwaggerSerializers classes.

We also need to bind our SwaggerServletContextListener class:

final class SwaggerServletContextListener implements 
    ServletContextListener {

  SwaggerServletContextListener() {}

  @Override
  public void contextInitialized(ServletContextEvent event) {

    BeanConfig beanConfig = getBeanConfig();
    event.getServletContext().setAttribute("reader", 
      beanConfig);
    event.getServletContext().setAttribute("swagger", 
      beanConfig.getSwagger());
    event.getServletContext().setAttribute("scanner", 
      ScannerFactory.getScanner());
  }

  private BeanConfig getBeanConfig() {

    BeanConfig beanConfig = new BeanConfig();
    beanConfig.setVersion("1.0.0");
    beanConfig.setSchemes(new String[] {"http"});
    beanConfig.setHost("localhost:8080");
    beanConfig.setBasePath("/api");

    beanConfig.setTitle("RESTEasy, Embedded Jetty, Swagger and " 
      + "Guice Example");
    beanConfig.setDescription("Sample RESTful API built using RESTEasy,"
      + " Swagger, Swagger UI and Guice packaged in a Fat JAR with an"
      + " embedded Jetty instance and no web.xml.");

    // setScan() must be called last
    beanConfig.setResourcePackage(
      MessageResource.class.getPackage().getName());
    beanConfig.setScan(true);

    return beanConfig;
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {}
}

Injectors

Modules are the building blocks of an injector (Guice's object-graph builder). Lets create an injector and use it to build our Main class:

public static void main(String[] args) throws Exception {

  try {
  
    final Injector injector = Guice.createInjector(new JettyModule(),
      new RestEasyModule(APPLICATION_PATH),
      new ResourceModule(), new SwaggerModule(APPLICATION_PATH));

    injector.getInstance(Main.class).run();

  } catch (Throwable t) {
    t.printStackTrace();
  }
}

We also need to update our run() method so that all requests will be routed through the Guice Filter:

public void run() throws Exception {

  final int port = 8080;
  final Server server = new Server(port);

  // Setup the basic Application "context" at "/".
  // This is also known as the handler tree (in Jetty speak).
  final ServletContextHandler context = new ServletContextHandler(
    server, CONTEXT_ROOT);

  // Add the GuiceFilter
  FilterHolder filterHolder = new FilterHolder(filter);
  context.addFilter(filterHolder, APPLICATION_PATH + "/*", null);

  // Setup the DefaultServlet at "/".
  final ServletHolder defaultServlet = new ServletHolder(
    new DefaultServlet());
  context.addServlet(defaultServlet, CONTEXT_ROOT);

  // Set the path to our static (Swagger UI) resources
  String resourceBasePath = 
    Main.class.getResource("/swagger-ui").toExternalForm();
  context.setResourceBase(resourceBasePath);
  context.setWelcomeFiles(new String[] { "index.html" });

  // Add any Listeners that have been bound
  eventListenerScanner.accept((listener) -> {
    context.addEventListener(listener);
  });

  final HandlerCollection handlers = new HandlerCollection();

  // The Application context is currently the server handler,
  // add it to the list.
  handlers.addHandler(server.getHandler());

  // Add any Handlers that have been bound
  handlerScanner.accept((handler) -> {
    handlers.addHandler(handler);
  });

  server.setHandler(handlers);
  server.start();
  server.join();
}

Build and run the example program

To build the 'Fat JAR Swagger Guice' example (in the examples/fatjar-swagger-guice directory) and run some tests:

mvn clean install

To run the example:

java -jar target/fatjar-swagger-guice-1.0-SNAPSHOT.jar

You should see output like:

19:25:05.748 [main] INFO  org.eclipse.jetty.util.log - 
  Logging initialized @1000ms
19:25:06.041 [main] INFO  org.eclipse.jetty.server.Server - 
  jetty-9.3.z-SNAPSHOT
19:25:07.450 [main] INFO  org.reflections.Reflections - 
  Reflections took 1281 ms to scan 1 urls, producing ...
19:25:07.979 [main] INFO  o.jboss.resteasy.plugins.guice.i18n - 
  RESTEASY011025: registering provider instance for
  io.swagger.jaxrs.listing.SwaggerSerializers
19:25:07.980 [main] INFO  o.jboss.resteasy.plugins.guice.i18n -
  RESTEASY011020: registering factory for 
  io.qbuddy.api.resource.MessageResource
19:25:08.016 [main] INFO  o.jboss.resteasy.plugins.guice.i18n - 
  RESTEASY011020: registering factory for 
  io.swagger.jaxrs.listing.ApiListingResource
19:25:08.075 [main] INFO  o.e.j.server.handler.ContextHandler - Started 
19:25:08.137 [main] INFO  o.e.jetty.server.AbstractConnector - Started
19:25:08.140 [main] INFO  org.eclipse.jetty.server.Server - Started

In your browser navigate to:

http://localhost:8080

You should see output like:

References:
Source Code: