Thursday, May 19, 2011

Spring MVC: Mixing annotation based and classic controllers

One of the improvements in Spring MVC 3 was a nifty namespace simplifying the configuration. If you're starting a new project and want to use the default setup your MVC setup pretty much fits on a napkin:
<beans>
  <mvc:annotation-driven />
  <context:annotation-driven />
  <bean:component-scan base-package="org.acm.steidinger"/>
  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
  </bean>
</beans>


Now I don’t know about you but I rarely have the pleasure to start a project with a clean slate. And if you already have an application which uses the old style of Spring MVC controllers, too many of them to port them to the new style overnight, you’ll have to have a setup which enables you to use the old and the new in parallel. In that case you can't use the namespace configuration and have to setup your MVC config explicitly.



Unfortunately the reference documentation leaves you on your own. There is no example for this kind of config and neither is there an explanation what <mvc:annotation-driven /> actually does. Of course there is always the source code, but it takes a bit of time to find out what exactly is going on. So I recently took the time to analyze the setup and here is my MVC configuration which allows me to mix annotation based controllers and validators with inheritance based controllers and validators:



<beans>
  <context:annotation-driven/>
  <bean:component-scan base-package="org.acm.steidinger"/>

  <bean id="localValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
  <bean id="annotationHandlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
  <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
  <bean id="methodHandlerAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
      <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
        <property name="validator" ref="localValidator" />
        <property name="conversionService" ref="conversionService" />
      </bean>
    </property>
  </bean>

  <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="/classic.htm">classicController</prop>
      </props>
    </property>
  </bean>
  <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
  <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="/WEB-INF/messages" />
  </bean>
  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
  </bean>

  <bean id="classicController" class="org.acm.steidinger.springExample.web.ClassicController">
    <property name="commandName" value="form" />
    <property name="formView" value="test" />
    <property name="successView" value="test" />
    <property name="validator">
      <bean class="org.acm.steidinger.springExample.web.ClassicFormValidator" />
    </property>
  </bean>
</beans>


The example above contains the beans necessary to setup the infrastructure as well as a single old style controller.  Pretty straightforward. The only part that was a bit tricky was the AnnotationMethodHandlerAdapter. For JSR 303 aka Bean Validation to work you need to configure the AnnotationMethodHandlerAdapter’s webBindingInitializer with a reference to the validator and conversionService. Otherwise everything will seem to be working but the validation will not work.