Thursday, May 31, 2012

Spring WebApplicationInitializer and programmatic support for web.xml file

Servlet 3.0 provides a programmatic way to specify and configure the contents of a web.xml file.

A servlet 3.0 based web application can implement a ServletContainerInitializer interface, which is explicitly passed a ServletContext which is a runtime representation of the container, to be used for configuring the servlets, filters, listeners and other contents normally specified through a typical web.xml file.

If a custom ServletContainerInitializer is provided, the implementation class name has to be declared in a META-INF/services/java.servlet.ServletContainerInitializer file in the classpath.

Spring 3.1+ makes this a process a little easier by providing an implementation of ServletContainerInitializer called the SpringServletContainerInitializer, registers this implementation through the META-INF/services/java.servlet.ServletContainerInitializer file in the spring-web module, and in turn delegates the responsibility of configuring the ServletContext to classes implementing a custom Spring interface called WebApplicationInitializer.

Essentially from a web application developers perspective the only thing that needs to be done is to implement a WebApplicationInitializer, and configure the ServletContext in the implementation. This is how a custom WebApplicationInitializer for a web application looks like for a typical Spring Application:

public class CustomWebAppInitializer implements WebApplicationInitializer {

 @Override
 public void onStartup(ServletContext container) {
  XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
  rootContext.setConfigLocations(new String[]{"classpath*:META-INF/spring/applicationContext-security.xml", "classpath*:META-INF/spring/applicationContext.xml"});

  container.addListener(new ContextLoaderListener(rootContext));

  ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", DispatcherServlet.class);
  dispatcher.setInitParameter("contextConfigLocation", "/WEB-INF/spring/webmvc-config.xml");
  dispatcher.addMapping("/");

  container.addFilter("Spring OpenEntityManagerInViewFilter", org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.class)
   .addMappingForUrlPatterns(null, false, "/*");

  container.addFilter("HttpMethodFilter", org.springframework.web.filter.HiddenHttpMethodFilter.class)
   .addMappingForUrlPatterns(null, false, "/*");

  container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
     .addMappingForUrlPatterns(null, false, "/*");

  FilterRegistration charEncodingfilterReg = container.addFilter("CharacterEncodingFilter", CharacterEncodingFilter.class);
  charEncodingfilterReg.setInitParameter("encoding", "UTF-8");
  charEncodingfilterReg.setInitParameter("forceEncoding", "true");
  charEncodingfilterReg.addMappingForUrlPatterns(null, false, "/*");
 }
}

A web.xml is still required though, to configure some things which cannot be completely configured using this mechanism, the stripped down web.xml looks like this:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="false">

    <display-name>my app</display-name>
    
    <description>my app</description>

    <context-param>
        <param-name>defaultHtmlEscape</param-name>
        <param-value>true</param-value>
    </context-param>
    
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/uncaughtException</location>
    </error-page>
    
    <error-page>
        <error-code>404</error-code>
        <location>/resourceNotFound</location>
    </error-page>
</web-app>

Make a note of the version attribute specified as 3.0, and the schema location being specified as "http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd", these are what trigger the container to consider this as a Servlet 3.0 web application.

That is basically it, with these in place the application can be considered to be configured - mostly without a web.xml file!

References:
http://blog.springsource.org/2011/06/10/spring-3-1-m2-configuration-enhancements/
Greenhouse application at Github - https://github.com/SpringSource/greenhouse - look in the servlet3 branch

7 comments:

  1. Thankyou for sharing, helped me iron out a couple of niggles moving from XML to Java config. :)

    ReplyDelete
  2. thanks, helpful post

    ReplyDelete
  3. This is awesome! Thanks for this!

    ReplyDelete
  4. One advantage, I understand, is having a less verbose or stripped down version of web.xml - are there any other advantages of using this programmatic way?

    ReplyDelete
    Replies
    1. Yes, (but the advantage is not for your benefit) you get to lock your company's code base into a third party product instead of using the standard Servlet API

      Delete