Close

Spring MVC - Understanding HandlerMapping

[Last Updated: Mar 7, 2018]

The interface HandlerMapping maps requested URLs to the handler methods/objects (such as the classes annotated with @Controller along with @RequestMapping methods).

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

public interface HandlerMapping {
     ......
  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

DispatcherServlet processes one or more HandlerMappings (in a specified order) to forward the request to the mapped handler object. For a request, once a HandlerMapping returns a non-null HandlerExecutionChain instance, it is used for the next step (i.e. invoking the handler) and more HandlerMapping objects are not attempted. According to DispatcherServlet.properties following handlers are registered by default:

  • BeanNameUrlHandlerMapping:

    Maps from URLs to beans (the controllers) by bean names. This only works if url starts with "/"

  • DefaultAnnotationHandlerMapping:

    Maps from URLs to handler classes/methods by matching @RequestMapping value element. Note that this class has been deprecated in favor of RequestMappingHandlerMapping, which provides flexibility and extensibility. More info here.

We can also print all registered handlers during runtime:

@Controller
public class TestController {
  
  @Autowired
  ApplicationContext context;
  
  @RequestMapping(value = "/test")
  @ResponseBody
  public String handleRequest () {
      Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerMapping.class, true, false);
      
      matchingBeans.forEach((k, v) -> System.out.printf("order:%s %s=%s%n",
                                                        ((Ordered) v).getOrder(),
                                                        k, v.getClass().getSimpleName()));
      return "response from /test";
  }
}

Accessing http://localhost:8080/test will print following output on the server console:

order:0 requestMappingHandlerMapping=RequestMappingHandlerMapping
order:2147483647 viewControllerHandlerMapping=EmptyHandlerMapping
order:2 beanNameHandlerMapping=BeanNameUrlHandlerMapping
order:2147483647 resourceHandlerMapping=EmptyHandlerMapping
order:2147483647 defaultServletHandlerMapping=EmptyHandlerMapping

The above list is different from DispatcherServlet.properties, that's because we are using @EnableWebMvc in our configuration class:

@EnableWebMvc
@Configuration
@ComponentScan
public class AppConfig {
}

@EnableWebMvc imports all configuration from DelegatingWebMvcConfiguration (a subclass of WebMvcConfigurationSupport). This configuration includes the registration of HandlerMappings. Check out this tutorial as well.

Note that, we also printed the order of handlers as well. DispatcherServlet process the handlers in the order provided by Ordered interface.

EmptyHandlerMapping (as seen in above output) is a static subclass of WebMvcConfigurationSupport which always returns null. It is processed at the end, if none matched (its order is Integer.MAX)

Using RequestMappingHandlerMapping

To have RequestMappingHandlerMapping map our handler methods, we need to use @RequestMapping annotation in the @Controller class. In this series of tutorials, we have mostly been utilizing the same HandlerMapping. Our above TestController also uses this annotation.

Using BeanNameUrlHandlerMapping

This maps requests by controller beans names. See an example here.

HandlerMapping settings

As all default HandlerMapping classes extend from AbstractHandlerMapping, they have following important common properties:

  • setInterceptors(Object... interceptors) Sets interceptors to apply for all matched mappings. Supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor.

  • setOrder(int order) All registered handler mappings are sorted in this order (ascending order). The mapping is applied for the first matching handler.

  • setAlwaysUseFullPath(boolean b) If true then the application is required to use full mapping path which includes the part specified by the parent servlet mapping. Default is false. See an example here.

  • setDefaultHandler(Object defaultHandler) A default handler to be used if this handler does not match. By default its value is null. See an example here.

How to customize a default HandlerMapping?

To customize one of the default HandlerMappings, we have to register it as a @Bean with exactly the same name as set by WebMvcConfigurationSupport, but the problem with this approach, we have to set all properties from scratch. Another preferred way is to implement WebMvcConfigurer and override a suitable method which configures one of the HandlerMappings (e.g. WebMvcConfigurer#configurePathMatch()). Another option (if others don't work) is not to use @EnableWebMvc and directly extend WebMvcConfigurationSupport. We have to override factory methods (annotated with @Bean) which set the default HandlerMapping bean. Check out this tutorial too.

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.0.1 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Default Handler Mappings Select All Download
  • spring-handler-mapping
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • TestController.java

    See Also