In developing frameworks or applications the Dependency Injection pattern aides in separating the "wiring" from the actual components. This article discusses how the pattern is implemented on the Servlet API which does not support Dependency Injection natively.
This article is targetted primarily to framework developers or those that use the Servlet API directly in their applications.
Dependency Injection is a popular pattern to manage component dependencies. It is the proper name for Inversion of Control or IoC for short . The pattern allows developers to delegate the task of "wiring" components together to a seperate Assembler component, this results in shorter classes, reduces/removes the need for the "fragile base class" anti-pattern, and promotes the use of interfaces over concrete classes. Another article by Martin Fowler provides a full description of the pattern.
There are many Dependency Injection implementations, one of the popular ones is called PicoContainer which primarily utilizes constructor based injection and has a very small footprint compared to others such as Spring which is a full J2EE framework.
The Servlet API is used in many J2EE projects and is the core of many popular frameworks such as Struts and Tapestry. I myself started a project to create another web framework as an exercise to understand the standard Servlet API. I called the framework Twiff.
In developing the Twiff framework I started with the typical servlets to handle certain calls. Common elements got abstracted out to a base class. The abstract base class was then converted into classes that provide the same service.
What I ended up was something like:
public class ViewServlet extends HttpServlet { private final Configuration configuration = new SimpleConfiguration(); private final Cache cache = new SimpleCache(configuration); ... }
This is fine assuming there is no need to change what is needed by ViewServlet. However, Simian (a code similarity analysis tool) started to flag it as a problem. The easiest way to resolve it was to put back the abstract base class so all the common code would move there, but then you'd have a more complex class heirarchy and maintenance would have to check two places to understand how things work. There is also a problem as concrete classes are known by the servlet rather than just using the interfaces. It would be nice if Configuration and Cache were injected into the servlet class rather than being created by the servlet class, but Servlet API does not support it natively.
PicoServlet solves this problem by allowing servlets to have dependencies injected into them. This is a Nirvana for servlet development according to the PicoContainer project. PicoServlet implements the Adapter pattern to adapt a servlet that requires construction dependency injection into a servlet that can be invoked by a standard servlet container.
Although it means that developers would have to write two classes to do what only one did before, the separation of responsibilities would make the code easier to maintain as wiring is separated from the actual use.
This section discusses the sequence in developing the PicoServlet. See Example of Using PicoServlet for an example of how to use PicoServlet. The test first development methodology is used when developing this.
Using HttpUnit allows developers to perform tests on the servlets. As a starting point, a test case to prove that a simple Hello world servlet using the standard APIs would return the proper result is created.
public void testNormalServlet() throws Exception { ServletRunner servletRunner = new ServletRunner(); servletRunner.registerServlet("NormalServlet", HelloWorldServlet.class.getName()); ServletUnitClient client = servletRunner.newClient(); WebRequest request = new GetMethodWebRequest("http://test/NormalServlet"); WebResponse response = client.getResponse(request); assertEquals("Hello", response.getText()); }
HelloWorldServlet is then coded which has a sample component that will be injected in a later version.
public class HelloWorldServlet extends HttpServlet { /** * Injected component. Though in this case it is not injected by explicity * added. */ private final InjectedComponent component = new SampleComponent(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write(component.getHello()); } }
Finally the interface to the injected component is created along with a sample implementation.
public interface InjectedComponent { String getHello(); } public class SampleComponent implements InjectedComponent{ public String getHello() { return "Hello"; } }
The test case should now run successfully. So now we have functionality to base line on.
A new test case showing how things would work using the PicoServlet is written. Again the HttpUnit framework is used to create the test case. Its is basically cut and paste coding with just the servlet name and class replaced.
public void testNormalServlet() throws Exception { ServletRunner servletRunner = new ServletRunner(); servletRunner.registerServlet("picoServ", SamplePicoServlet.class.getName()); ServletUnitClient client = servletRunner.newClient(); WebRequest request = new GetMethodWebRequest("http://test/picoServ"); WebResponse response = client.getResponse(request); assertEquals("Hello", response.getText()); }
A stub for SamplePicoServlet that implements a servlet is created first. In the first iteration it simply forwards calls to SamplePicoServletImpl that it creates.
public class SamplePicoServlet implements Servlet { private final SamplePicoServletImpl delegate = new SamplePicoServletImpl(new SampleComponent()); public final void service(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { delegate.service(request, response); } ... }
Now SamplePicoServletImpl has to be created. It is basically a copy of HelloWorldServlet with the component defined in the constructor.
public class SamplePicoServletImpl extends HttpServlet { public SamplePicoServletImpl(final InjectedComponent component) { this.component = component; } private final InjectedComponent component; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write(component.getHello()); } }
The the test case should now run properly. The Dependency Injection pattern will be put in the next section.
In this section we modify the code to make things more generic and use PicoContainer to manage the dependencies. The test cases are still valid so there is no need to create new ones. This is just a refactoring of SamplePicoServlet.
The first thing we have to do is to pull up all the methods from SamplePicoServlet to an abstract class PicoServlet that do not specifically refer to SamplePicoServletImpl. Which in our case is all of them but the constructor.
public class SamplePicoServlet extends PicoServlet { public SamplePicoServlet() { super(new SamplePicoServletImpl(new SampleComponent())); } } public abstract class PicoServlet implements Servlet { private Servlet delegate; public PicoServlet(Servlet delegate) { this.delegate=delegate; } ... }
The test case should still run successfully after this change. However, the PicoContainer is still not yet used.
If PicoContainer would wire the dependencies the delegate's class has to be passed rather than the actual object. To register any other components that may be needed by the delegate, a populateContainer() method must be created and extended by SamplePicoServlet. The initialization also needs to be done in the init() method in order for the ServletConfig to be passed in. With all those requirements known we may now code finish the code.
public class SamplePicoServlet extends PicoServlet { public SamplePicoServlet() { super(SamplePicoServletImpl.class); } public void populateContainer(final MutablePicoContainer container) { container.registerComponentImplementation(SampleComponent.class); } } public abstract class PicoServlet implements Servlet { private final Class<? extends Servlet> delegateServletClass; private Servlet delegate; public PicoServlet(Class<? extends Servlet> delegateServletClass) { this.delegateServletClass=delegateServletClass; } public final void init(final ServletConfig servletConfig) throws ServletException { this.servletConfig = servletConfig; container = new DefaultPicoContainer(); container.registerComponentImplementation(delegateServletClass); populateContainer(container); delegate = (Servlet) container.getComponentInstanceOfType(Servlet.class); delegate.init(getServletConfig()); } ... }
This is basically how PicoServlet was written. However, there is more that needed to be done in order to take advantage of life cycles in PicoContainer and to prevent cyclic dependency with the ServletConfig. There are also the Filters and Listeners that can also be wrapped to use PicoContainer. However, full sources are available at http://twiff.sf.net/modules/picoservlet/.
Here are the sources minus the imports and package declarations that show what needs to be created when using the API.
The servlet that you register in web.xml
public class SamplePicoServlet extends PicoServlet { public SamplePicoServlet() { super(SamplePicoServletImpl.class); } public void populateContainer(final MutablePicoContainer container) { container.registerComponentImplementation(SampleComponent.class); } }
The actual servlet that contains your logic.
public class SamplePicoServletImpl extends HttpServlet { public SamplePicoServletImpl(final InjectedComponent component) { this.component = component; } private final InjectedComponent component; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write(component.getHello()); } }