Wednesday, March 2, 2016

Java-based Spring mvc configuration - without Spring security

Spring 3+ supports using java-based configuration in replacement of the tradictional web.xml. You can replace web.xml with any class which implments interface org.springframework.web.WebApplicationInitializer ( which only has one onStartup() method).

Usually all you need to provide to a web.xml for a spring mvc application are:

  1. Spring Config for root context
  2. Spring config for servlet context   (for spring mvc, it's config for org.springframework.web.servlet.DispatcherServlet)
  3. Where to map spring's DispatcherServlet (ususally "/")
  4. Filters, if any

To facilitate usage of this WebApplicationInitializer  interface, Spring also provide serveral Abstract classes, which has methods directly ask for informations list above. The most used is class org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer

In this article, there are 2 demos, first uses WebApplicationInitializer, second uses AbstractAnnotationConfigDispatcherServletInitializer. Spring 4.2 is used in the demos.

This article only show basics when Spring security is not used. For more practical usage with Spring security, please check another article on "Java-based Spring mvc configuration  - with Spring security". 

0. Java config classes for root and servlet context

Both demos use same Java config classes to config root and servlet context.

The java config for root context is named RootConfig.java.

package com.shengwang.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import ch.qos.logback.classic.helpers.MDCInsertingServletFilter;

@Configuration
@EnableWebMvc
@ComponentScan({"com.shengwang.demo.service","com.shengwang.demo.model"})
public class RootConfig {
  
  // logback's filter for easy logging info. e.g client's IP
  @Bean
  public MDCInsertingServletFilter mdcInsertingServletFilter() {
    return new MDCInsertingServletFilter();
  }
}

The root context here has service and model beans registerd, but in practical it's really not that strict. Also a filter bean proviced by logback is configured in the root context. This "mdcInsertingServletFilter" bean is actually a filter and will be used later.

The java config for dispatcher servlet is called DispatcherConfig.java.

package com.shengwang.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


@Configuration
@EnableWebMvc
@ComponentScan("com.shengwang.demo.controller")

public class DispatcherConfig extends WebMvcConfigurerAdapter{
  
  // To use jsp
  @Bean
  ViewResolver internalViewResolver() {
    // the view resolver bean ...
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/");
    resolver.setSuffix(".jsp");
    return resolver;
  }
  
  // config static resources
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // Don't forget the ending "/" for location or you will hit 404.
    registry.addResourceHandler("/img/**").addResourceLocations("/static/images/");
    registry.addResourceHandler("/js/**").addResourceLocations("/static/js/");
    registry.addResourceHandler("/css/**").addResourceLocations("/static/css/");
  }
}

The servlet context contain controller beans and a view resolver bean to render JSP page, just for demo. Usually it extends WebMvcConfigurerAdapter, so you can configure web mvc specific behaviours, such as static resource mapping or async support by overriding corresponding methods. (WebMvcConfigurerAdapter workds as default web mvc config with a bunch of empty methods)

The content of the RootConfig.java and DispatcherConfig.java are list here for completeness of the demos. Just remember RootConfig.java for root context, DispatcherConfig.java for servlet context. They are used in following demos. For both demos, the hierrarchy looks like below.

image

 

1. Demo One - Use WebApplicationInitializer directly

The MyWebApplicatonInitializerWithoutSpringSecurity.java implements interface WebApplicationInitializer. Spring mvc doesn't need this class to be a bean, no @Bean  or @Component annotation for this class.

package com.shengwang.demo;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;


public class MyWebApplicatonInitializerWithoutSpringSecurity implements WebApplicationInitializer {
  
  @Override
  public void onStartup(ServletContext container) throws ServletException {
    useRootContext(container);
    useDispatcherContext(container);
    addFilter(container);
  }

  private void useRootContext(ServletContext container) {
    // Create the 'root' Spring application context
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootConfig.class); // <-- Use RootConfig.java

    // Manage the lifecycle of the root application context
    container.addListener(new ContextLoaderListener(rootContext));
  }
  
  private void useDispatcherContext(ServletContext container) {
    // Create the dispatcher servlet's Spring application context
    AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
    dispatcherContext.register(DispatcherConfig.class); // <-- Use DispatcherConfig.java

    // Define mapping between <servlet> and <servlet-mapping>
    ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(
        dispatcherContext));
    dispatcher.addMapping("/");
    dispatcher.setLoadOnStartup(1);
  }

  private void addFilter(ServletContext container) {
    String filterName = "WhatEverYouWantToNameYourFilter";
    String filterBeanName = "mdcInsertingServletFilter";
    container.addFilter(filterName, new DelegatingFilterProxy(filterBeanName)).addMappingForUrlPatterns(null, false, "/");
  }
}

2. Demo Two - Use AbstractAnnotationConfigDispatcherServletInitializer for simplicity

Define own class extends from this abstract class.

package com.shengwang.demo;

import javax.servlet.Filter;

import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


public class MyWebApplicationInitializerWithoutSpringSecurity extends AbstractAnnotationConfigDispatcherServletInitializer {

  // return null, if you don't use both root and servlet context
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[] {RootConfig.class};
  }
  
  // return null, if you don't use both root and servlet context
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[] {DispatcherConfig.class};
  }
  
  // map dispatcherServlet to "/"
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  // returned filters will be register to dispatcherServlet
  @Override
  protected Filter[] getServletFilters() {
    // bean defined in RootConfig.java
    String beanNameOfFilter = "mdcInsertingServletFilter";
    return new Filter[] { new DelegatingFilterProxy(beanNameOfFilter) };
  }
}

There are 4 override  mothods, each has a every clear purpose.

3.  Misc

To use java-based spring mvc to replace web.xml, don't forget to config maven war plugin to ignore the "web.xml missging error" in the pom.xml.

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.2</version>

    <!-- ignore missing web.xml error -->
    <configuration>
      <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
  </plugin>

See Also

Simplified Web Configuration with Spring  by Josh Long

1 comment:

  1. How will you configure Logback with AbstractAnnotationConfigDispatcherServletInitializer ??

    ReplyDelete

Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List