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:
- Stackoverflow: How to install the OpenJDK for Java 1.8
- RESTEasy Docs (3.0.19.Final): RESTEasy JAX-RS
- Ben Mane's gist: Swagger 1.5.1-M1 + RestEasy 3.x + Guice 4
- GWizard: GWizard's Swagger module
- Wikipedia: The SOLID principle
Source Code:
- GitHub: Getting started with RESTEasy