Migrate from pure Servlet+JSP to Spring MVC

This post describes steps for smooth migrating from simple pure Servlet to SpringMVC. I'm just pointing similar way of doing something using web.xml or basic Servlet syntax and doing same using Spring.

web.xml

With SpringMVC we could get rid of using xml to configure things.

Session

Session configuration in web.xml

<session-config>
    <session-timeout>3</session-timeout>
    <cookie-config>
        <http-only>true</http-only>
        <secure>true</secure>
    </cookie-config>
</session-config>

Here is way of doing this using Spring. First of all create WebListener:

@WebListener
public class SessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        httpSessionEvent.getSession().setMaxInactiveInterval(3*60);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {

    }
}

And add it to the WebApplicationInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. The key is in line with container.addListener()

public class SpringWebAppInitializer implements WebApplicationInitializer {

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

        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(ApplicationContextConfig.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
        ServletRegistration.Dynamic dispatcher = container.addServlet(
                "SpringDispatcher", dispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        container.addListener(new SessionListener()); // <-- <session-timeout>3</session-timeout>
    }
}

To control cookie parameters we have to implement Interceptor in our @Configuration class:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.classes")
public class ApplicationContextConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptorAdapter() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                     Object handler) throws Exception {
                request.setCharacterEncoding("UTF-8");
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json");

                final Cookie cookie = new Cookie("timestamp", Long.toString(new Date().getTime()));
                cookie.setSecure(true);   // <--  <secure>true</secure>
                cookie.setHttpOnly(true); // <--  <http-only>true</http-only>
                response.addCookie(cookie);

                return super.preHandle(request, response, handler);
            }
        });
        super.addInterceptors(registry);
    }
}
HTTP Method restriction

Here is example from web.xml

<!--Disable dangerous HTTP methods-->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>restricted methods</web-resource-name>
        <url-pattern>/*</url-pattern>
        <http-method>TRACE</http-method>
        <http-method>PUT</http-method>
        <http-method>OPTIONS</http-method>
        <http-method>DELETE</http-method>
        <http-method>CONNECT</http-method>
    </web-resource-collection>
    <auth-constraint />
</security-constraint>

To achieve this in Spring we will add an "Access-Control-Allow-Methods header configuration in our Interceptor. Now it should look like:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.example.classes") public class ApplicationContextConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new HandlerInterceptorAdapter() {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                 Object handler) throws Exception {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");

            response.addHeader("Access-Control-Allow-Methods", "POST, GET"); <--  HTTP header restrictions

            final Cookie cookie = new Cookie("timestamp", Long.toString(new Date().getTime()));
            cookie.setSecure(true);   // <--  <secure>true</secure>
            cookie.setHttpOnly(true); // <--  <http-only>true</http-only>
            response.addCookie(cookie);

            return super.preHandle(request, response, handler);
        }
    });
    super.addInterceptors(registry);
}
Error handling

Error handling configuration in web.xml

<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/error</location>
</error-page>
<error-page>
    <error-code>404</error-code>
    <location>/error.jsp</location>
</error-page>
<error-page>
    <error-code>403</error-code>
    <location>/error.jsp</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/error.jsp</location>
</error-page>

In Spring we have to implement so called ControllerAdvice error handler:

@ControllerAdvice
public class GlobalErrorHandler {
    public static final String DEFAULT_ERROR_VIEW = "error";

    @ExceptionHandler(value = Throwable.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest request, Exception throwable) throws Exception {
        // If the exception is annotated with @ResponseStatus rethrow it and let
        // the framework handle it - like the OrderNotFoundException example
        // at the start of this post.
        // AnnotationUtils is a Spring Framework utility class.
        if (AnnotationUtils.findAnnotation(throwable.getClass(), ResponseStatus.class) != null)
            throw throwable;

        // Otherwise setup and send the user to a default error-view.
        final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        final String servletName = (String) request.getAttribute("javax.servlet.error.servlet_name");

        final String fullURL = getFullURL(request);
        final String clientIP = getClientIpAddr(request);

        final StringWriter errors = new StringWriter();
        throwable.printStackTrace(new PrintWriter(errors));
        throwable.printStackTrace();

        // TODO do something with the error and show error page

        return new ModelAndView(DEFAULT_ERROR_VIEW);
    }

Little quote about ControllerAdvice from spring.io

A controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.

Filter mappings

Here is typical filter chain in web.xml

<filter>
    <filter-name>CacheControl</filter-name>
    <filter-class>com.example.filters.DefaultCachingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CacheControl</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>OneYearCachingFilter</filter-name>
    <filter-class>com.example.filters.OneYearCachingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OneYearCachingFilter</filter-name>
    <url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>OneYearCachingFilter</filter-name>
    <url-pattern>*.css</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>OneYearCachingFilter</filter-name>
    <url-pattern>*.png</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>OneYearCachingFilter</filter-name>
    <url-pattern>*.jpeg</url-pattern>
</filter-mapping>

In Spring we will use FilterRegistration in WebApplicationInitializer. Now our WebApplicationInitializer will look like:

public class SpringWebAppInitializer implements WebApplicationInitializer {

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

        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(ApplicationContextConfig.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
        ServletRegistration.Dynamic dispatcher = container.addServlet(
                "SpringDispatcher", dispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        container.addListener(new SessionListener()); // <-- <session-timeout>3</session-timeout>
    }

        // Filter chain starts here
        FilterRegistration.Dynamic twoMinutes = container.addFilter("TwoMinutes", new DefaultCachingFilter());
        twoMinutes.addMappingForUrlPatterns(null, false, "/*");

        FilterRegistration.Dynamic oneYear = container.addFilter("OneYear", new OneYearCachingFilter());
        oneYear.addMappingForUrlPatterns(null, false, "*.js");
        oneYear.addMappingForUrlPatterns(null, false, "*.css");
        oneYear.addMappingForUrlPatterns(null, false, "*.jpeg");
        oneYear.addMappingForUrlPatterns(null, false, "*.png");

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

}

Servlets

Usually Servlet code looks like below:

/** News Servlet. Will show some news **/
@WebServlet
        name = "NewsServlet",
        urlPatterns = {"/news"}
)
public class NewsServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        // The other pre-process code to show news
    }
}
/** Message Servlet. Will show messages **/
@WebServlet
        name = "MessagesServlet",
        urlPatterns = {"/messages"}
)
public class MessagesServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        // The other preprocess code to show messages
    }
}

Please note that without Spring you have to configure response and request in each Servlet separately. In Spring all request and response configuration could be moved to our Interceptors:

@Controller
@RequestMapping("/")
public class MainController{

    @RequestMapping(value = "/news", method = RequestMethod.GET)
    public ModelAndView showNews(HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        // The other pre-process code to show news
        return new ModelAndView("news");
    }

    @RequestMapping(value = "/messages", method = RequestMethod.GET)
    public ModelAndView showMessages(HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        // The other preprocess code to show messages
        return new ModelAndView("messages");
    }
}

Conlcusion

Migration to Spring is easy. Spring reduces usage of xml configuration file and reduce a lot boilerplate code.