19 June 2013
Most people know that Spring's long supported Servlet 3 Java-based initializer classes. This facility builds on Servlet 3's javax.servlet.ServletContainerInitializer
facility. Basically, you declare a class that implements org.springframework.web.WebApplicationInitializer
and this class will be scanned by Spring on application startup and bootstrapped. This class has one responsibility: assemble the web application's moving parts, like you would in a web.xml
, but in code. Here's an example:
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ServiceConfiguration.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
dispatcherServlet.register(WebMvcConfiguration.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
In the above example, WebConfiguration
and ServiceConfiguration
are Java configuration classes. ServiceConfiguration
presumably describes how the service tier - with its data sources and transaction managers and thread pools and ORM mapping - are assembled. The WebMvcConfiguration
class describes how the web tier - supported by Spring MVC - is put together. We use the ServletContext
to build up the web application. But, a lot of this is procedural. We will have basically the same arrangement each time, simply plugging in different values as required.
The point of this post isn't to describe how Java configuration works, rather to introduce the WebApplicationInitializer
implementation, called org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
.
To use it, you simply extend it and satisfy fill in the blinks (in the form of template methods). Here's an example:
import com.jl.crm.services.ServiceConfiguration;
import org.springframework.context.annotation.*;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.*;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import javax.servlet.*;
import java.io.File;
public class CrmWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
private int maxUploadSizeInMb = 5 * 1024 * 1024; // 5 MB
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{ServiceConfiguration.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{RepositoryRestMvcConfiguration.class, WebMvcConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new HiddenHttpMethodFilter(), new MultipartFilter(), new OpenEntityManagerInViewFilter()};
}
@Override
protected void registerDispatcherServlet(ServletContext servletContext) {
super.registerDispatcherServlet(servletContext);
servletContext.addListener(new HttpSessionEventPublisher());
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
File uploadDirectory = ServiceConfiguration.CRM_STORAGE_UPLOADS_DIRECTORY;
MultipartConfigElement multipartConfigElement =
new MultipartConfigElement(uploadDirectory.getAbsolutePath(),
maxUploadSizeInMb, maxUploadSizeInMb * 2, maxUploadSizeInMb / 2);
registration.setMultipartConfig(multipartConfigElement);
}
}
In typical Spring fashion, we have callbacks where Spring can't possibly devine our intents (it can't possibly know what the names of our configuration classes are, for example) but otherwise it's all taken care of for us. I had to override the default ServletRegistration.Dynamic
instance so I could tailor where file uploads are stored by the Servlet 3 container, which I do in the customizeRegistration
method. The rest of the code is fairly straightforward: Spring registers the Filter
s provided by the getServletFilters
method, it registers the configuration classes returned from the getRootConfigurationClasses
as part of a global ContextLoaderListener
, and it registers the configuration classes returned from the getServletFilters
as part of the DispatcherServlet
. This reflects the typical layering in most Spring applications: global beans and services that are potentially to be shared among multiple DispatcherServlet
implementations are stored in the ContextLoaderListener
, and DispatcherServlet
-local beans are configured there. My web configuration class is shown below. I show this mainly for completeness, but you can see I've registered and configured .jsp
support, as well as multipart file upload support (taking advantage of the Servlet 3-powered StandardServletMultipartResolver
). The other thing worth mentioning is that we've turned on Spring HATEOAS with the @EnableHypermediaSupport
.
@Configuration
@ComponentScan
@EnableWebMvc
@EnableHypermediaSupport
class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Bean
public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
@Bean
public ViewResolver internalResourceViewResolver() {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/crm/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
}