Sunday, November 28, 2010

Spring MVC pitfall - overriding default beans

We all learn the whole life :) My last lesson was related to overriding default beans registered by mvc namespace parsers. It all started when I read Customizing WebDataBinder initialization section of Spring Framework 3.0 documentation. You can read there something like this:
To externalize data binding initialization, you can provide a custom implementation of the WebBindingInitializer interface, which you then enable by supplying a custom bean configuration for an AnnotationMethodHandlerAdapter, thus overriding the default configuration.
Encouraged by this citation I registered my own bean along the usual <mvc:annotation-driven />
<mvc:annotation-driven />

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer" ref="MyWebBindingInitializer" />
</bean>
    
<bean class="[...].DefultWebBindingInitializer" id="MyWebBindingInitializer" />
At this point I've encountered first problem - the order of beans registration matters in the above case - first registered handler will be used. This is reasonable, one AnnotationMethodHandlerAdapter is registered by Spring while processing <mvc:annotation-driven /> and one by me. First registered wins :)

I've moved my bean before <mvc:annotation-driven /> and started enjoying its work. After few days I was preparing some functionality returning one value from the handler method, which should be converted automagically to JSON response (see my posts: JSON - Jackson to the rescue and JSON - Jackson Serialization Narrowed). To my surprise, usual configuration didn't worked, I started digging through my own and Spring Framework code, and found the reason (which in fact can be also deduced from mvc:annotation-driven documentation) - my own bean didn't registered the same message converters as parser responsible for processing mvc:annotation-driven (AnnotationDrivenBeanDefinitionParser) and especially one needed by me: MappingJacksonHttpMessageConverter.

At this point I had few possibilities to solve this problem, I could for example register appropriate message converter myself, but I decided to go a different way, because solving the problems generated ourselves, is worse than avoiding those problems :)

I've prepared BeanPostProcessor implementation correcting the one property required by me:
public class MVCConfigurationPostProcessor implements BeanPostProcessor {

    private WebBindingInitializer webBindingInitializer;

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof AnnotationMethodHandlerAdapter) {
            ((AnnotationMethodHandlerAdapter) bean).setWebBindingInitializer(webBindingInitializer);
        }
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }   

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

}
Then I've corrected my Spring configuration to:
<bean class="[...].MVCConfigurationPostProcessor">
    <property name="webBindingInitializer" ref="MyWebBindingInitializer" />
</bean>
    
<bean id="MyWebBindingInitializer" class="[...].DefultWebBindingInitializer" />
    
<mvc:annotation-driven />
Finally I was able to enjoy both AnnotationMethodHandlerAdapter using my own WebBindingInitializer and JSON conversion working correctly :).

4 comments:

  1. Oh, so you have blog...

    BTW, you may want to have a look at flexjson, I really like it: http://flexjson.sourceforge.net/

    Cheers,

    ReplyDelete
  2. Blog is definitely too big word ;) I heard that people write their Blogs for the Fortune and Glory ;) - I don't think this blog will bring me Fortune, maybe at least a little bit of Glory ;) - I rather treat it like a bunch of yellow cards, on which I can write my thoughts ;)

    Thanks for the FlexJSON link, have you used it with the Spring Framework maybe?

    greetings for you, and Prozz, if he is still somewhere around :)

    ReplyDelete
  3. Another work around is to give assign your custom handler with an order property of a lower value i.e higher property than the one registered by the .

    The DispatcherServlet picks your handler first. The order of beans in the config file matters when they have the same priority

    ReplyDelete