Another Java web framework? Not really…
There are dozens of web technologies for Java (Struts, Stripes, Tapestry, Wicket, GWT, Spring MVC, Vaadin, Play, plain old servlets and JSP, Dropwizard, etc.) and they all have their advantages and disadvantages.
This article isn’t about comparing frameworks. Instead, I’ll describe how to serve HTTP requests from Java without using a monolithic framework in a way that’s a lot more pleasant than using just HttpServletRequests: Jetty for handling low-level HTTP things, Jersey for request routing, Jackson for serialization and Guice to tie it all together.
Update: If you actually want to write something quickly using stuff shown in this article, Dropwizard has since come out, and looks like a solid choice. If you want to learn more about how to build such a stack, check out the second article in this series.
If you want to write your web UI on the server side (whether it’s translated to JavaScript ala GWT or simply HTML markup like you might put in a JSP), this probably isn’t an approach that will be very attractive. You won’t have a web framework already there to provide pre-canned view helpers or login forms or “is this a phone number” validation or any of that. If you are transitioning towards making the server be more of a data store and putting display logic entirely in the client (regardless of whether the client is a browser or a native mobile app), the no-big-framework approach can make a lot of sense. You don’t need portlets or JavaScript components when you’re simply shoving JSON or XML or what-have-you over HTTP. I’ll be including code samples as new concepts are introduced, but if you want to browse the finished example project, it’s on the Team Lazer Beez GitHub site.
Jetty
Jetty is a HTTP server and servlet container that’s been designed expressly for easy embedding. I think it’s easier to do the edit-restart-test cycle with a simple main method that starts a Jetty server (no IDE I’ve used has ever had integration with a servlet container that works as fast or as reliably as running a main method). If you prefer to run and debug inside a separate container, or if you need container-managed security or JTA or XA or any of that stuff, feel free bundle your code as a war instead of using Jetty. Here’s how you start a Jetty server listening on localhost:8080.
Server server = new Server(8080); // handlers go here server.start();
Pretty easy! But, this server doesn’t do anything since we haven’t told it what to do for incoming requests. So, before we call start()
, we need to give the server a Handler
. Handlers are a Jetty concept that can do pretty much anything in response to a HTTP request. You might have a Handler
that only gathers performance metrics or only does logging. In this case, we want a Handler
that can invoke servlets.
ServletContextHandler handler = new ServletContextHandler(); handler.setContextPath("/"); // set up handler server.setHandler(handler);
One odd thing about Jetty’s ServletContextHandler
is that it likes to have one servlet always, even though in our case we won’t actually end up needing any to run our business logic. So, we’ll give the handler a servlet (InvalidRequestServlet
) that always 404s.
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setContentType("text/plain"); resp.setContentType("UTF-8"); resp.getWriter().append("404"); }
If you create a main method with the code to start up Jetty and run it, you should now be able to go to http://localhost:8080 in your browser and see the result of your InvalidRequestServlet
. Before we can start adding some actual logic, though, I’ll need to explain Guice and its servlet layer.
Guice
Guice is a dependency injection framework. Though it accomplishes more or less the same job that Spring does, it has a slightly different philosophy. Whereas Spring tends to think in terms of bean ids when gluing code together, Guice tends to think in terms of types. They’re both good tools with different strengths, but I am only going to show how to use Guice (or this article will never end!). I’ll give a whirlwind tutorial in Guice and Guice Servlet and then we’ll get back to building our stack.
Making some PB&J Sandwiches
One of the problems (but not the only one) that DI/IoC frameworks like Guice and Spring are trying to solve is the problem of passing an instance through many layers of constructors just because some deep-down class is structured to take an instance of some interface. No doubt you’ve seen this anti-pattern: Class1
instantiates Class2
which instantiates Class3
, but Class3
takes a Foo
instance in its constructor, so somewhere higher up (perhaps in Class1
, or even higher up than that) you create the correct Foo
instance and then it gets passed around for a while until it’s finally used in Class3
. This sucks, but it’s a worthy goal to have Class3
take a Foo
instance in its constructor rather than directly instantiating some concrete implementation: this benefits testability, separation of concerns, etc.
Guice can help make this easier. As an example, let’s suppose that you have a SandwichMaker
class that takes a PeanutButter
implementation in its constructor. For testing your SandwichMaker
, you may wish to use a mock PeanutButter
implementation, but in an actual deployment you might wish to use an OrganicCrunchyValenciaPeanutButter
(OCVPB for short).
class SandwichMaker { // ... SandwichMaker(PeanutButter peanutButter) { this.peanutButter = peanutButter; } // ... } interface PeanutButter { void applyToSandwich(Sandwich sandwich, int grams); }
Now the question is where in your actual app do you instantiate your OCVPB? It’s not ideal to do it in the class that creates a SandwichMaker
since that class will now be using actual peanut butter when it’s run during tests as it’s hardcoded the concrete implementation. It’s also not ideal to create the OCVPB in the top-level main method and pass the instance around through however many levels of constructors are needed to get to the SandwichMaker
. To explain how we can use Guice to solve this problem, there are a few concepts to explain.
Guice concepts
Rather than explicitly creating instances with the new
keyword, Guice lets you define how classes depend on each other and takes care of wiring things together for you. In our case, we want our SandwichMaker
to have a PeanutButter
implementation provided to it. We inform Guice of this by annotating the ctor with @Inject
. (There are versions of Inject
and some other classes from both com.google.inject
and from the standardized JSR 330 javax.inject
packages. They work almost exactly the same; check the Guice documentation for details. Just pick one and stick with it.)
class SandwichMaker { // ... @Inject SandwichMaker(PeanutButter peanutButter) { this.peanutButter = peanutButter; } // ... }
Now that we’ve said that SandwichMaker
needs a PeanutButter
, we need to say which PeanutButter
to use. We do this with a binding in a module. Modules are intended to represent functional boundaries in your application. The decision of when to put things in separate modules is like learning when to put things in separate packages: you’ll simply need to get a feel for it by trying it out. For now, let’s create a module that sets up everything you need to make sandwiches.
class SandwichModule extends AbstractModule { @Override protected void configure() { bind(PeanutButter.class).to(OrganicCrunchyValenciaPeanutButter.class); } }
This informs Guice to create a new instance of OCVPB whenever it sees a request to inject a PeanutButter
instance. PeanutButter
seems like the sort of thing that should be a singleton since it represents a real-life resource, though, so let’s go ahead and fix that:
bind(PeanutButter.class).to(OrganicCrunchyValenciaPeanutButter.class).in(Scopes.SINGLETON);
We could also have annotated our OCVPB implementation class with @Singleton
to achieve the same effect. Now, no matter how many SandwichMaker
s we get, only one PeanutButter
will be used. We create an Injector
with all of the modules necessary (in this case, just the one module) and use it to get an instance of SandwichMaker
. It will inspect the bindings and injection targets and create a OCVPB and pass it to the SandwichMaker
ctor. We don’t actually need to get a standalone SandwichMaker
instance, but if we did, this is how we would do it.
Injector injector = Guice.createInjector(new SandwichModule()); SandwichMaker maker = injector.createInstance(SandwichMaker.class); // use the maker
This is just the very most basic usage of Guice. Check out their documentation for more details.
Guice Servlet
Now that we know how to bind instances (whether as singletons or new-one-every-time) and inject them, we can move on to Guice Servlet. This extension to Guice allows us to get rid of web.xml entirely (if embedding Jetty) or make it much simpler (if using a war). Suppose you have a FooServlet
servlet. In the old days of web.xml
, there would be servlet
tags and servlet-mapping
tags to wire it up. Here’s how it is with Guice Servlet:
class FooServletModule extends ServletModule { @Override protected void configureServlets() { bind(FooServlet.class); serve("/foo").with(FooServlet.class); // other servlets } }
Since Guice is instantiating your FooServlet
class intead of relying on the servlet container to invoke the 0-args ctor, this also means that you can use @Inject on the ctor and get the objects you need that way instead of pulling them out of init params or servlet context. To feed incoming requests into the servlets you’ve laid out with Guice Servlet, you need to set up GuiceFilter
as a filter for all requests. You can do this in web.xml if you’re using a war (it’s the only thing you’ll actually need in web.xml
) or just configure Jetty directly.
Injector injector = Guice.createInjector(new SandwichModule(), new AbstractModule() { @Override protected void configure() { binder().requireExplicitBindings(); bind(GuiceFilter.class); } }); FilterHolder guiceFilter = new FilterHolder(injector.getInstance(GuiceFilter.class)); handler.addFilter(guiceFilter, "/*", EnumSet.allOf(DispatcherType.class));
Now, you can stop here and bind your servlets in a ServletModule
and get the benefits of being able to initialize your servlets with Guice, but it gets even easier with Jersey.
Jersey and Jackson
Jersey is the reference implementation of JAX-RS. It lets you do things like POST
the url “http://localhost:8080/foo?bar=baz&quux=1234″ and handle the request with this class:
@Path("/foo") @Produces(MediaType.APPLICATION_JSON) class FooResource { @POST public String handleFooPost(@QueryParam("bar") String bar, @QueryParam("quux") int quux) { return "{\"yay\":\"hooray\"}"; } }
The class, method and parameter names are not magic; only the annotations are what ties this code to handling that request. Jersey will set the status code, content length, etc. appropriately. It can also do void methods (returns status 204 for you), streaming responses, and entirely custom responses (pick the status code, the entity, etc), so check the documentation for the details. If you use Jersey to handle actually calling your business logic instead of writing plain old servlets directly, you only need to have one servlet binding in your Guice Servlet module:
public class SandwichServletModule extends ServletModule { @Override protected void configureServlets() { // bind resource classes here // hook Jersey into Guice Servlet bind(GuiceContainer.class); // hook Jackson into Jersey as the POJO <-> JSON mapper bind(JacksonJsonProvider.class).in(Scopes.SINGLETON); serve("/*").with(GuiceContainer.class); } }
GuiceContainer
is a class that ships with Jackson. When you use GuiceContainer
, all you need to do is bind the resource classes (like FooResource
above) with Guice. The JacksonJsonProvider
binding to make it easier to return objects as JSON; you’ll see what it does a bit later. To go back to our sandwich example, let’s suppose we have two resources: one to make sandwiches and one to say how many sandwiches have been made. Let’s start with the stats one (at /sandwich/stats
) since it is simpler (doesn’t need to modify state). Supposing I have a SandwichStats
class that can provide StatSnapshot
objects that represent the total amount of jam, peanut butter and sandwiches, the resource is very simple:
@Singleton @Produces(MediaType.APPLICATION_JSON) @Path("/sandwich/stats") public class SandwichStatsResource { private final SandwichStats sandwichStats; @Inject SandwichStatsResource(SandwichStats sandwichStats) { this.sandwichStats = sandwichStats; } @GET public SandwichStats.StatsSnapshot getStats() { return sandwichStats.getStats(); } }
You’ll also need to bind the resource class so that GuiceContainer
can find it. When you have a lot of resources, you might want to put their bindings in a module just for that purpose, but for now just add a binding in SandwichServletModule
:
bind(SandwichStats.class);
The resource requests a SandwichStats
object in its ctor so that it can ask it for the latest info every time a request is made. The SandwichStats
object should also be a singleton since we want it to live as long as the app is up. (Guice is easy to use for non-singletons too — it just happens that in this contrived example I seem to be ending up with lots of singletons.) Note that the @GET
method returns an object, not a string. This is where the ImmutableMap.of(JSONConfiguration.FEATURE_POJO_MAPPING, "true")
comes in. It tells Jersey to attempt to map the POJO returned from the method to JSON. To do this, we use the Jackson JSON library. Jackson is a fast JSON serialization/deserialization library, and one of its features is easy annotation-based configuration for POJO mapping. It’s probably easiest to just show what it looks like in practice:
public class StatsSnapshot { // ... @JsonProperty public int getSandwichesMade() { return sandwichesMade; } @JsonProperty public int getGramsOfJam() { return gramsOfJam; } @JsonProperty public int getGramsOfPeanutButter() { return gramsOfPeanutButter; } }
When serialized by Jackson, an object of that class will end up as a JSON object with three keys (“gramsOfJam”, “gramsOfPeanutButter” and “sandwichesMade”). Since Jersey has been configured to automatically use Jackson to serialize objects, you can go to /sandwich/stats
in your browser and get something like this: {"gramsOfJam":150,"gramsOfPeanutButter":200,"sandwichesMade":1}
Now let’s add a resource that lets you make sandwiches. Normally I wouldn’t have something that modifies state be a GET but I want this to be easy to test in a browser, so let’s say that a GET
to http://localhost:8080/sandwich/create
will make a sandwich (with optional jam and peanutButter query parameters to specify how many grams of jam and peanut butter to use) and that the sandwich created should be returned as JSON. This is what such a resource might look like:
@Singleton @Produces(MediaType.APPLICATION_JSON) @ThreadSafe @Path("/sandwich/create") public class SandwichMakerResource { private final SandwichMaker sandwichMaker; private final SandwichStats sandwichStats; @Inject SandwichMakerResource(SandwichMaker sandwichMaker, SandwichStats sandwichStats) { this.sandwichMaker = sandwichMaker; this.sandwichStats = sandwichStats; } @GET public Sandwich makeSandwich(@QueryParam("jam") @DefaultValue("100") int gramsOfJam, @QueryParam("peanutButter") @DefaultValue("200") int gramsOfPeanutButter) { Sandwich sandwich = sandwichMaker.makeSandwich(gramsOfPeanutButter, gramsOfJam); sandwichStats.recordSandwich(sandwich); return sandwich; } }
The resource (yet another singleton) gets SandwichMaker
and SandwichStats
instances injected, and whenever it makes a sandwich it updates the SandwichStats object. Try accessing it via your browser a few times and check the stats page to see that the counts do update correctly. I’ve only scratched the surface of what these libraries can do with this simple example. Let me know if you think there’s some cool feature I missed (as long as it won’t complicate things too much to work it in).