Dependency Injection is that magic thing that aids design and testability. Unfortunately Servlets have never been injectable components. Well not according to Sun anyway. Many years ago we saw to it that the excellent Jetty could handle Servlets with constructors specifying their argument requirements. Time has come to talk about it and the required death of web.xml.

Obviously servlets, filters and listeners in web.xml must have an empty constructor, - its the container (tomcat, weblogic, websphere etc) that is instantiating them with no knowledge of anything other than what is specified in web.xml and the fact that it has a HTTP request to route to something. So the answer to this is to not use web.xml, nor let the servlet container control things. Jetty is the leading example of a servlet container that allows for embedding. Here is an example bringing up a servlet by hand, and having that servlet take component dependencies and configuration via a constructor:

import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.servlet.Context;

import javax.servlet.http.*;
import javax.servlet.ServletException;
import java.io.IOException;

public class Foo {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        Context root = new Context(server, "/", Context.SESSIONS);
        root.addServlet(new ServletHolder(new FooServlet("Mikey", "Mouse", new Dependency1())), "/*");
        server.start();
    }
}

public class FooServlet extends HttpServlet {
    private final String config1;
    private final String config2;
    private final Dependency1 dep1;
    public FooServlet(String config1, String config2, Dependency1 dep1) {
        this.config1 = config1;
        this.config2 = config2;
        this.dep1 = dep1;
    }
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/plain");
        resp.getWriter().println("config1=" + config1);
        resp.getWriter().println("config2=" + config2);
        dep1.recordGet(req.getRemoteHost());
        resp.getWriter().println("Remote host " + req.getRemoteHost() + " recorded");
    }
}

public class Dependency1 {
    public void recordGet(String remoteHost) {
        System.out.println("Get Page Impression - " + remoteHost);
    }
}

Of course, you are going to hard-code new FooServlet(..) if you only have a few servlets. If you’ve got hundreds with different arguments, then using a DI container like PicoContainer could be a way of reducing the lines of code, while keeping the general injectable design:

public class Foo {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        Context root = new Context(server, "/", Context.SESSIONS);
        MutablePicoContainer mpc = new PicoBuilder().withCaching().build();
        mpc.addConfig("config1", "Mickey")
           .addConfig("config2", "Mouse")
           .addComponent(Dependency1.class)
           .addComponent(FooServlet.class); // some for other servlets
        root.addServlet(new ServletHolder(mpc.getComponent(FooServlet.class)), "/*"); // same for others
        server.start();
    }
}

(don’t forget that PicoContainer 2.x can leverage parameter names to help resolve ambiguities - ref config1&config2 above) If we go one stage further, we can automagically map config from the command line to component needs.

public class Foo {
    public static void main(String[] args) throws Exception {
        PicoContainer apc = new ArgumentativePicoContainer(args);
        MutablePicoContainer mpc = new PicoBuilder(apc).withCaching().build();
        mpc.addComponent(Dependency1.class)
           .addComponent(FooServlet.class); // some for other servlets
        Server server = new Server((Integer) mpc.getComponent("port"));
        Context root = new Context(server, "/", Context.SESSIONS);
        root.addServlet(new ServletHolder(mpc.getComponent(FooServlet.class)), "/*"); // same for others
        server.start();
    }
}
...
java -cp <the required jars> Foo port=8080 config1=foooo config2=Mouse 

Then, one last one that’s more natural to the Jetty style, letting Jetty initiate the instantiation of the servlet itself. It has the same command line launch mechanism as above. This is also skeletal way that WebContainer actually works.

public class Foo {
    public static void main(String[] args) throws Exception {
        PicoContainer apc = new ArgumentativePicoContainer(args);
        MutablePicoContainer dependencies = new PicoBuilder(apc).withCaching().build();
        dependencies.addComponent(Dependency1.class)
           .addComponent(FooServlet.class); // some for other servlets
        Server server = new Server((Integer) dependencies.getComponent("port"));
        Context root = new Context(server, "/", Context.SESSIONS);
        root.addServlet(new FooServletHolder(dependencies, FooServlet.class), "/*"); // same for others
        server.start();
    }
}
class FooServletHolder extends ServletHolder {
    private final PicoContainer depenencies;
    private final Class servletClass;
    public FooServletHolder(PicoContainer depenencies, Class servletClass) {
        this.depenencies = depenencies;
        this.servletClass = servletClass;
    }
    public synchronized Object newInstance() throws InstantiationException, IllegalAccessException {
        return new TransientPicoContainer(depenencies).addComponent("svt", servletClass).getComponent("svt");
    }
}

It has to be said that the most people code Java web applications with a framework of some sort these days, and not servlets directly. I recommend Mike Ward’s Waffle over others, for many reasons, but the most relevant to this discussion is its internal construction is a set of DI components. With a couple of small modifications, it could pass a parent PicoContainer in, and components on to the actions/controllers themselves.

So I’d like to see more of the library-like instantiation of web applications, and less of the WAR style (web.xml) way. I think its good for Java future. This style may be another nail in the coffin of J2EE, but that’s not a bad thing. Removing the mysticism of what the servlet container does, and when it does it, is necessary to making server-side Java ordinary. Ten years on there is little to recommend of J2EE it seems, and Sun are busy baking more needless stuff into the spec.

It is a great shame that constructor injection never made it into EJB 3.0. The reason I heard was”it was considered too hard for the container makers to be able to deliver that functionality”. If it had, and to servlets too, we may have been at this point a lot earlier.

There is much related work:

Cargo is a great too that attempts to abstract all the servlet containers into some common embeddable APIs.

We coded a piece for NanoContainer called WebContainer that provides wrapper classes for Jetty that are automatic Dependency Injection aware. It allows for a library like way of having trees of injection aware servlets. filters and listeners. It even has a Groovy wrapper for a builder-like way of declaring that same tree (port to JRuby in progress).

Before that we had a project at Apache called ‘Sevak’ (2002), later it was moved to SourceForge (as the Avalon project was imploding), then on to Codehaus as ‘Jervlet’ (with a complicated OSS license”portions of this source are copyright to Apache …. portions of this source is copyright to …”). In fact one of the original versions is still visible in a now defunct pre-Spring open source ‘enterprise IoC’ project Enterprise Object Broker (EOB) Oct 2002. That version used Avalon rather than Dependency Injection for component resolution and generally and tried to wrap multiple servlet containers (like Cargo does today). Sevak was Pico and Avalon compatible in 2003, but it proved to be too hard to maintain (two dimensions of abstractions were too painful for the end user) so Jervlet then NanoContainer’s WebContainer capability ended up being DI only.

Google can assist in the vague historical search if you are really interested - http://www.google.com/search?q=apache+sevak http://www.google.com/search?q=jervlet

Lastly, the PicoContainer team had some fun this year when we upgraded WebContainer to a version of Jetty later than 6.0.1. Our injection into servlets just stopped working. After some fretting, we traced it to the some changes that Greg and Jan made for the Geronimo team. Refer changes around this one . Accidentally Jan and Greg took out the changes they’d made for us a couple of years before (dependency injection container support for servlets etc) and put in something for Dain and the gang (dependency injection container support for servlets etc). The same but different way :-). When we got our Mortbay buddies to make the changes, we should have supplied a testcase too - and a nice comment linking to the PicoContainer gang. The moral being - if it hasn’t got a test case, it isn’t a feature.



blog comments powered by Disqus

Published

July 22nd, 2007
Reads:

Categories