The project I'm currently working on uses Spring MVC. This framework is nice, mature and efficient.

But we are using String as return type of our @RequestMapping methods (as most people probably do) and I don't like that very much.

I want to use and enum instead. I want @RequestMapping methods to return a enum constant which will be automatically resolved to a view the same way a String is resolved to a view.

Using an enum is good

Using String as return value of controller methods to represent view name is bad: * bad for maintenance * bad for refactoring * obviously not type-safe * adds magic numbers to your code

Using an enum has many positive side effects : * view names are all in the same place * if it easy to tell which view is used or not with any IDE * refactoring is much easier

How to add support for an enum return type in Spring MVC

create a enum with a String property

The string property will hold the String value @RequestMapping methods used to return.

public enum MyView {
  HOME("home"),
  LOGIN("login"),
  CUSTOMER_LIST("customer/list")  ;

  @Nonnull
  private final String logicalViewName;

  private ExtranetView(@Nonnull String logicalViewName) {
    this.logicalViewName = Preconditions.checkNotNull(logicalViewName);
  }

  @Nonnull
  public String getLogicalViewName() {
    return logicalViewName;
  }
}

create a HandlerMethodReturnValueHandler

If we make our @RequestMapping methods return a value of MyView and run our website, Spring will simply add the value to the model and won't resolve a view and fail.

To fix, that, we need to provide with an extra HandlerMethodReturnValueHandler which will "convert" our enum to its String property. To be more accurate, we need to set the viewName in the ModelAndViewContainer of the current request.

public class MyViewEnumModelAndViewResolver implements HandlerMethodReturnValueHandler {

  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    return MyView.class.isAssignableFrom(returnType.getParameterType());
  }

  @Override
  public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    if (returnValue == null) {
      return;
    }
    if (returnValue instanceof MyView) {
      String viewName = ((MyView) returnValue).getLogicalViewName();
      mavContainer.setViewName(viewName);
    }
    else {
      // should not happen
      throw new UnsupportedOperationException("Unexpected return type: " +
        returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
    }
  }
}

(inspiration: Spring's org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler class)

add our HandlerMethodReturnValueHandler to the Spring MVC's servlet context

After some investigation on the web, I found that the easiest way of adding an extra HandlerMethodReturnValueHandler to the Spring MVC servlet context is to use programmatic configuration.

I found a like to this bug report https://jira.springsource.org/browse/SPR-8648 which has a useful comment pointing to the source code of Spring's Greenhouse Reference Application.

Since I already had some XML-based configuration in place, I created a Configuration class which referes to my XML config and therefor is pretty simple :

@Configuration
@EnableWebMvc
@ImportResource({
  "classpath:/META-INF/spring/my-dispatcher-servlet.xml",
})
public class MyMvcConfig extends WebMvcConfigurerAdapter {
  @Override
  public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
    returnValueHandlers.add(
      new MyViewEnumModelAndViewResolver()
    );
  }

}

What's important here :

  • @Configuration annotation is obviously mandatory
  • @EnableWebMVC annotation is also required so that the WebMvcConfigurerAdapter interface we extends is taken into account
  • the WebMvcConfigurerAdapter provides empty implementations of the WebMvcConfigurer interface methods
  • we can then override only those we need, in our case : addReturnValueHandlers
  • the @ImportResource is here to load our legacy XML configuration
  • existing XML configuration should be usable as is with one very important retriction :
    • the <annotation-driven/> tag of the MVC XML namespace should be remove as the @EnableWebMVC annotation is its exact programmatic equivalent. Not doing so will most likely make Spring fail to load your context, but the error you would get will moke likely not obviously point to the <annotation-driven/> tag.

Conclusion

This solution is working like a charm and I like it very much. Spring easy extensibility was a real pleasure to discover.

I wonder if this solution could be made generic and bundle into Spring MVC...


Published

Category

articles

Tags

Contact