Getting rid of web.xml in Spring MVC App

Apr 02, 2018
Getting rid of web.xml in Spring MVC App
From Servlet 3.0 on, web.xml is optional. How to get rid of it in your Spring MVC app and what is the replacement?

web.xml and Servlet 3.0

Web.xml, also known as deployment descriptor, is traditionally used as a configuration file for Java web applications. It defines servlets, their mappings, servlet filters, lifecycle listeners and more. Originally it was the only way to provide such configuration. Over the time, once popular XML configuration lost its appeal and popularity in favor of Java-based annotation configuration. The same trend can also be observed in Spring Framework.

With Servlet version 3.0, the deployment descriptor is no longer mandatory. The configuration originally provided by the file can now be provided by annotations such as @WebServlet, @WebFilter or @WebListener directly on the class level. For example, you can annotate your servlet like this to provide mapping and init params:

@WebServlet(value="/my-servlet",
            initParams = {
                @WebInitParam(name="first", value="some-value")
                @WebInitParam(name="second", value="some-other-value")
            })
public class MyServlet extends HttpServlet {
  ...
}

For more info about replacing web.xml with annotations see Servlet 3.0 Tutorial: @WebListener, @WebServlet, @WebFilter and @WebInitParam.

web.xml and Spring MVC

In Spring MVC, web.xml used to be the place, where you needed to declare and configure Dispatcher Servlet, which is a Front Controller, receiving all the requests and dispatching to all the other components such as Controllers. Fortunately, Spring offers a convenient, XML-free way of declaring Dispatcher Servlet.

Interface WebApplicationInitializer

In version 3.1, Spring introduced WebApplicationInitializer. It is an interface, which you can implement. If you do so, Spring will detect your class and execute its method onStartup(ServletContext container), inside which you can define Configuration of your Dispatcher Servlet and do all the other config such as registering and mapping of other servlets, filters or lifecycle listeners. Most importantly, inside the method, you'll need to create your application context. This usually means having root application context and web application context. The specific class you'll need to use for your app context will depend whether you still use XML configuration or Java configuration.

XML Config

To create application context based on XML configuration files, you'll need to use XmlWebApplicationContext.

public class XmlWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
        rootContext.setConfigLocation("/WEB-INF/config/root-context.xml");

        container.addListener(new ContextLoaderListener(rootContext));

        XmlWebApplicationContext dispatcherContext = new XmlWebApplicationContext();
        dispatcherContext.setConfigLocation("/WEB-INF/config/servlet-context.xml");

        ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

Java Config

If you are already using Java Configuration files in favor of XML or you want to migrate, you'll need application context which will load the configuration from annotation-based Java classes. You'll need to use AnnotationConfigWebApplicationContext instead of XmlWebApplicationContext.

public class AnnotationWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class);

        container.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebConfig.class);

        ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

More servlets, filters and listeners

So far so good, our Dispatcher Servlet is configured. But web.xml is not used only for the dispatcher, but for all the servlets, their filters, listeners and more. How to configure these? Just call a corresponding method on the ServletContext object, which is an input parameter of the method:

@Override
public void onStartup(ServletContext container) throws ServletException {

    // Add a Listener
    container.addListener(MyListener.class);
    
    // Add a Filter
    FilterRegistration.Dynamic filter = container.addFilter("FilterName", MyFilter.class);
    filter.addMappingForUrlPatterns(null, true, "/*");
    
    //Add a Servlet
    ServletRegistration.Dynamic servlet = container.addServlet("ServletName", MyServlet.class);
    servlet.setLoadOnStartup(2);
    servlet.addMapping("/foo", "/bar");
    
    ...
}

WebApplicationInitializer implementations

Like in the examples above, you can implement WebApplicationInitializer interface all by yourself and you have complete freedom. However, Spring already provides several implementations you can build on. They are usually abstract classes implementing the interface. They take opinionated approach providing most of the configuration for you letting you specify the details. They basically act as templates forcing you to implement necessary details and allowing you to override the rest if necessary.

For example, there is SpringBootServletInitializer for Spring Boot applications. For regular Spring web apps, there is AbstractAnnotationConfigDispatcherServletInitializer. It will create and register Dispatcher Servlet and root and web application context based on Java classes with annotations. You provide configuration classes and URLs to which Dispatcher Servlet should be mapped to:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    //Provide configuration classes for Root App Context
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    //Provide configuration classes for Web App Context
  }

  @Override
  protected String[] getServletMappings() {
    //Provide URLs to which Dispatcher Servlet Should be mapped to
  }
}

Of course, if you need to customize how Dispatcher Servlet is created or any other details, you can override methods from the parent class.

Updating your Maven WAR plugin

After removing web.xml file, your Maven build may break complaining that the file is missing. This happens when you're using an older version of Maven WAR Plugin. You can set configuration flag failOnMissingWebXml to false to fix the error.

<build>
    <plugins>
        <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>    
            </configuration>
        </plugin>
    </plugins>
</build>

Or better yet, if you upgrade your plugin version to 3+, the default value of failOnMissingWebXml is now set to false and does not need to be explicitly specified anymore.

Conclusion

If you have a legacy Spring MVC application, which still uses web.xml, you can remove the file if you're on Servlet 3.0+. One option is to implement WebApplicationInitializer directly. This gives you the most flexibility, but all the work is on you. Another option is to use any of the abstract classes provided by Spring, which implement the interface. In most cases, it is the preferred solution as it leaves you with less manual configuration and it uses reasonable opinionated defaults.

Either way, Java configuration is in many cases preferable as it gives you more flexibility and unlike in XML, you can use conditional logic based on current circumstances.




Let's connect