SyntaxHighlighter

Friday, March 11, 2011

Customizing Spring 3 mvc:annotation-driven

In Spring 3, it is very easy to configure an application with all the basic MVC components using the mvc:annotation-driven tag.

The default configuration can be customized with arguments to the mvc:annotation-driven tag (such as "validator" and "conversion-service") and other tags in the mvc: namespace. In Spring 3.0.0 the set of customization options was somewhat limited, but it has grown with each Spring release. Spring 3.1 adds a mvc:message-converters to address one of the more common customization needs.

A very common mistake developers make when they need to customize the annotation-driven configuration is to use mvc:annotation-driven and also manually define a AnnotationMethodHandlerAdapter bean with customized properties, like this:
<mvc:annotation-driven/>

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
 <property name="customArgumentResolver">
   <bean class="com.example.ExampleArgumentResolver" />   
 </property>
  <property name="messageConverters">
    <list>
      <bean id="marshallingHttpMessageConverter"
            class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
            p:marshaller-ref="marshaller" p:unmarshaller-ref="marshaller"/>
    </list>
  </property>
</bean>

This does not work as expected. With this configuration, you end up with two AnnotationMethodHandlerAdapter beans in your application context - one created by mvc:annotation-driven and the one created manually. The one created by the framework is used, and the one created manually is ignored.

One working solution to this problem is to remove the mvc:annotation-driven tag and instead manually define all the annotation support beans. This is not ideal, as there is not a good way to keep up with all the beans defined automatically by mvc:annotation-driven as the annotation support evolves in the framework.

The preferred solution to the customization problem is to implement a BeanPostProcessor to modify properties of the AnnotationMethodHandlerAdapter bean created by mvc:annotation-driven in-place, instead of replacing it. This is not hard to do, and provides all the flexibility needed to customize the annotation support.

Here is an example of what this solution can look like. This solution uses a very flexible class that has all the same properties as AnnotationMethodHandlerAdapter, but configures an existing HandlerAdapter instead of creating a second one. Usage of this solution would look like this:

<mvc:annotation-driven/>

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterConfigurer" init-method="init">
 <property name="customArgumentResolver">
   <bean class="com.example.ExampleArgumentResolver" />   
 </property>
  <property name="messageConverters">
    <list>
      <bean id="marshallingHttpMessageConverter"
            class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
            p:marshaller-ref="marshaller" p:unmarshaller-ref="marshaller"/>
    </list>
  </property>
</bean>

Look closely at line 3 in the second example, since this is the only line that is different from the first example. The bean being created is a AnnotationMethodHandlerAdapterConfigurer instead of a AnnotationMethodHandlerAdapter. The Configurer class manipulates the HandlerAdapter already created by the framework, but is configured just like AnnotationMethodHandlerAdapter. The "init-method" argument to the bean definition is important, as it causes the configuration to happen after the application context is up and running.

Here is what the Configurer looks like:
public class AnnotationMethodHandlerAdapterConfigurer {
  @Autowired
  private AnnotationMethodHandlerAdapter adapter;

  private WebBindingInitializer webBindingInitializer;
  private HttpMessageConverter[] messageConverters;
  private PathMatcher pathMatcher;
  private UrlPathHelper urlPathHelper;
  private MethodNameResolver methodNameResolver;
  private WebArgumentResolver[] customArgumentResolvers;
  private ModelAndViewResolver[] customModelAndViewResolvers;

  private boolean replaceMessageConverters = false;

  public void init() {
    if (webBindingInitializer != null) {
      adapter.setWebBindingInitializer(webBindingInitializer);
    }

    if (messageConverters != null) {
      if (replaceMessageConverters) {
        adapter.setMessageConverters(messageConverters);
      } else {
        adapter.setMessageConverters(mergeMessageConverters());
      }
    }

    if (pathMatcher != null) {
      adapter.setPathMatcher(pathMatcher);
    }

    if (urlPathHelper != null) {
      adapter.setUrlPathHelper(urlPathHelper);
    }

    if (methodNameResolver != null) {
      adapter.setMethodNameResolver(methodNameResolver);
    }

    if (customArgumentResolvers != null) {
      adapter.setCustomArgumentResolvers(customArgumentResolvers);
    }

    if (customModelAndViewResolvers != null) {
      adapter.setCustomModelAndViewResolvers(customModelAndViewResolvers);
    }
  }

  private HttpMessageConverter[] mergeMessageConverters() {
    return (HttpMessageConverter[])
                  ArrayUtils.addAll(messageConverters, adapter.getMessageConverters());
  }

  public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
    this.webBindingInitializer = webBindingInitializer;
  }

  public void setPathMatcher(PathMatcher pathMatcher) {
    this.pathMatcher = pathMatcher;
  }

  public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    this.urlPathHelper = urlPathHelper;
  }

  public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
    this.methodNameResolver = methodNameResolver;
  }

  public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
    this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
  }

  public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
    this.customArgumentResolvers = argumentResolvers;
  }

  public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
    this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
  }

  public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
    this.customModelAndViewResolvers = customModelAndViewResolvers;
  }

  public void setMessageConverters(HttpMessageConverter[] messageConverters) {
    this.messageConverters = messageConverters;
  }

  public void setReplaceMessageConverters(boolean replaceMessageConverters) {
    this.replaceMessageConverters = replaceMessageConverters;
  }
}

There is one property of the Configurer class that does not correspond directly to a property of AnnotationMethodHandlerAdapter: "replaceMessageConverters". By default this is false, which causes any configured MessageConverters to be added to the converters already configured into the AnnotationMethodHandlerAdapter. If this property is set to true, then the automatically-configured MessageConverters are thrown away and only those injected into the Configurer will be used.

This approach will become less and less interesting as Spring 3.1 is released and mvc:annotation-driven continues to evolve. Until then, it solves a problem that a lot of developers have spent time figuring out the hard way.

A Better Spring Meta-validator

Spring MVC 3 has a very rich set of features for binding and validating input data to command objects, including class-based validation and annotation-based validation.

Custom class-based validators can be registered with Spring MVC in one of two ways (as described in the documentation): configured in each @Controller class using the @InitBinder method, or configured at the application level using the "mvc:annotation-driven" tag with a "validator" argument. Using the per-controller approach gives lots of control when each Controller needs its own validators, but adding all those @InitBinder methods can become tedious (and therefore error-prone).

In a previous post, I showed a method for creating a meta-validator that could be configured as the global application-level validator, and would in turn delegate to other configured validators as appropriate for the command object type. This post is a refinement if that idea, giving more control over which validators are automatically discovered and globally available.

This flexibility is achieved by using annotations on the validators that should be globally available through the meta-validator, as in this example (stolen from the Spring documentation):


The first line is the part that is new - the @ValidatorComponent annotation. I don't love the name, but "Validator" was already taken and having the interface and annotation share a name would be confusing.

With the supporting code below, any classes that extend org.springframework.validation.Validator and also have the @ValidatorComponent annotation on them are automatically detected by the meta-validator and called when appropriate. Validators without the annotation are ignored by the meta-validator, and can be configured individually in @InitBinder methods if required.

Here is the code that makes this work. First, the annotation itself:


This annotation is annotated with @Component, which means validators using this annotation are also eligible for component scanning.

And, at last, the meta-validator class:


Using @Autowired with a collection as a way to get all beans of a certain type was a fairly recent discovery for me, and I like it much better than the older method of extending ApplicationContextAware and asking the ApplicationContext for "beansOfType". Otherwise this code is pretty straight-forward - it just scans the list of injected Validator beans and remembers any that have the annotation on them.

The setValidators() method is provided for the case where third-party validators also need to be registered with the meta-validator, such as the LocalValidatorFactoryBean needed to configure JSR-303 bean validation support.

Here is an example of a Spring XML configuration file that wires all this together:


That's it! Now all you have to do is write your validator classes (and their unit tests), annotate them with @ValidatorComponent, and let the framework do the rest.