Spring @MVC configuration without XML
XML is no longer hip. Actually, there is nothing as unhip as last year’s hip. That is until it becomes hip again 30 years later in failed irony.
Honestly, if you’ve been programming Java EE since the 90s, you know full well how error prone XML config files can be. A glance at an EJB XML config file – especially the CMP relationship config from version 2 – would make any sane person run screaming into the night. Tools were supposed to help, but they really didn’t.
Even the Spring Framework, a music major’s wildly successful solution to Java EE’s problems, has been inundated by XML config files. Until now.
Let’s see how we can configure a Spring @MVC web app with no XML.
Java EE update
Starting with the Servlet 3.0 specification, we can do away with the venerable WEB-INF/web.xml configuration file. In order to do this you must do the following:
- Write a class that implements
javax.servlet.ServletContainerInitializer
- Create a file named META-INF/services/javax.servlet.ServletContainerInitializer which contains the fully qualified name of your implementation.
Spring
Beginning with release 3.1, Spring provides an implementation of the ServletContainerInitializer interface aptly named SpringServletContainerInitializer. Take a peek inside spring-web-[version 3.1 and above].jar and you’ll see the META-INF/services file mentioned above.
The SpringServletContainerInitializer class delegates to an implementation of
org.springframework.web.WebApplicationInitializer that you provide. There is just one method that you need to implement: WebApplicationInitializer#onStartup(ServletContext). You are handed the ServletContext that you need to initialize.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class WebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) { WebApplicationContext appContext = set up the context ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(appContext)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/"); } } |
Setting up the app contexts
Since we are avoiding writing XML config files, I suggest that instead of using the XmlWebApplicationContext class, use the AnnotationConfigWebApplicationContext which supports classpath scanning for Spring annotation based configuration. You can explicitly add configuration classes, or have the context scan for them.
To start up and shut down the context, we add a ContextLoaderListener.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@Override public void onStartup(ServletContext servletContext) throws ServletException { // Create the root appcontext AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); rootContext.register(RootConfig.class); // since we registered RootConfig instead of passing it to the constructor rootContext.refresh(); // Manage the lifecycle of the root appcontext servletContext.addListener(new ContextLoaderListener(rootContext)); servletContext.setInitParameter("defaultHtmlEscape", "true"); // now the config for the Dispatcher servlet AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext(); mvcContext.register(WebMvcConfig.class); // The main Spring MVC servlet. ServletRegistration.Dynamic appServlet = servletContext.addServlet( "appServlet", new DispatcherServlet(mvcContext)); appServlet.setLoadOnStartup(1); Set<String> mappingConflicts = appServlet.addMapping("/"); if (!mappingConflicts.isEmpty()) { for (String s : mappingConflicts) { logger.error("Mapping conflict: " + s); } throw new IllegalStateException( "'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14"); } } |
Java config files
The RootConfig Java class we specified in the previous example needs to use the @Configuration annotation. Essentially this class corresponds to the <beans> element in Spring XML config files. The <bean> elements are now methods with the @Bean annotation that return a bean instance. The <context:component-scan> element is now the class level annotation @ComponentScan.
1 2 3 4 5 6 7 8 9 10 |
@Configuration @ComponentScan(basePackages = { "com.rockhoppertech.mvc.service", "com.rockhoppertech.mvc.repositories.internal" }) public class RootConfig { @Bean public SomeClass someClass() { return someInstance; } } |
Filters
You can also create any Servlet Filters here. Here are a few Spring provided Filters.
In this example I set up the CharacterEncodingFilter.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override public void onStartup(ServletContext servletContext) throws ServletException { ... FilterRegistration.Dynamic fr = servletContext.addFilter("encodingFilter", new CharacterEncodingFilter()); fr.setInitParameter("encoding", "UTF-8"); fr.setInitParameter("forceEncoding", "true"); fr.addMappingForUrlPatterns(null, true, "/*"); ... } |
@Enable*
XML namespaces are replaced by class level annotations that begin with @Enable.
- @EnableWebMvc
- @EnableAsync
- @EnableScheduling
- @EnableLoadTimeWeaving
- @EnableTransactionManagement
Right now we configure MVC using the mvc: XML namespace, for example: <mvc:annotation:driven/>. The @EnableWebMvc annotation is the replacement.
Spring MVC configuration
Write a configuration class that contains the @EnableWebMvc annotation, which is defined by DelegatingWebMvcConfiguration. In addition, you will probably do a component scan for controllers here.
To customize the defaults, implement WebMvcConfigurer or extend WebMvcConfigurerAdapter. Any overridden method that does not return NULL will use that value instead of the default.
1 2 3 4 5 6 7 8 |
@Configuration @EnableWebMvc // scan for controllers @ComponentScan(basePackages = { "com.rockhoppertech.mvc.web" }) public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean ViewResolver viewResolver() { ... } @Bean MessageSource messageSource() { ... } etc. |
Thanks for the section about filters declaration
Thanks for sharing this, well written and easy to follow. I’ve got rid of the plethora of spring and hibernate xml files and now I’ve just binned my web.xml. Woohoo! 😀
Glad it was of help, Ben
Thanks for the part about Filters!