HandlerMapping Overview

The HandlerMapping component parses a Request and finds a Handler that handles the Request, which is generally understood as a method in the Controller.

The HandlerMapping component does two main events.

  1. when the component is initialized, it registers the Request request and the corresponding Handler, which actually means that the Request and the corresponding Handler exist in a map as a key-value pair.
  2. parse each Request request and find the corresponding handler in the registered map.

In the SpringMvc source code, HandlerMapping is defined as an interface. In addition to defining several property fields, the interface defines only one getHandler method.

1
2
3
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request)
                                    throws Exception

HandlerMapping class system

HandlerMapping class system

As you can see from the above class diagram, the HandlerMapping component is divided into two families. One family inherits from AbstractHandlerMethodMapping, and the other family inherits from AbstractUrlHandlerMapping. Both abstract classes inherit from AbstractHandlerMapping.

AbstractHandlerMapping

AbstractHandlerMapping is an abstract class that implements the HandlerMapping interface, and it is a very basic class from which all subclasses of HandlerMapping are inherited. AbstractHandlerMapping uses the template pattern for the overall design, and each subclass overrides the template methods to achieve the appropriate functionality.

Since AbstractHandlerMappin abstract class inherits the HandlerMapping interface, it must implement the getHandler method. In the AbstractHandlerMappin class, the specific code is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
    * Look up a handler for the given request, falling back to the default
    * handler if no specific one is found.
    * @param request current HTTP request
    * @return the corresponding handler instance, or the default handler
    * @see #getHandlerInternal
    */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // Ensure presence of cached lookupPath for interceptors and others
    if (!ServletRequestPathUtils.hasCachedPath(request)) {
        initLookupPath(request);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    }
    else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }

    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {
            config.validateAllowCredentials();
        }
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

Special attention should be paid to the getHandlerInternal(request) method in the getHandler method, which is a boilerplate method. In the AbstractHandlerMapping class, the getHandlerInternal(request) method is just an abstract method that doesn’t do anything. This method is specifically reserved for subclasses of AbstractHandlerMapping to override and thus implement their own business logic.

AbstractHandlerMapping also inherits from the WebApplicationObjectSupport class and overrides the initApplicationContext method of that parent class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
    * Initializes the interceptors.
    * @see #extendInterceptors(java.util.List)
    * @see #initInterceptors()
    */
@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

In the initApplicationContext method, three methods are defined.

  1. extendInterceptors(**this**.interceptors) extendInterceptors is a template method that gives subclasses an entry point to modify this.interceptors interceptors.

  2. detectMappedInterceptors(**this**.addedInterceptors) detectMappedInterceptors method is to add all beans of type MappedInterceptor in Spring MVC to the collection of this.addedInterceptors.

  3. initInterceptors() initInterceptors is to initialize the interceptors, add all the interceptors wrapped in the this.interceptors collection to the this.adoptedInterceptors collection.

AbstractHandlerMethodMapping series

AbstractHandlerMethodMapping

AbstractHandlerMethodMapping is a very important class. In addition to inheriting from AbstractHandlerMapping abstract class, AbstractHandlerMethodMapping also implements the InitializingBean interface.

Handler registration

In Spring if a class implements the InitializingBean interface, the Spring container will call the afterPropertiesSet method of the bean when it is instantiated.

The AbstractHandlerMethodMapping class does the initialization and registration work when it overrides the afterPropertiesSet method of the InitializingBean interface. This is the first step of the HandlerMapping component, registering the Request request and the corresponding Handler first.

The afterPropertiesSet method of the AbstractHandlerMethodMapping class is shown in the following code.

1
2
3
4
5
6
7
8
/**
    * Detects handler methods at initialization.
    * @see #initHandlerMethods
    */
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

You can see that the initHandlerMethods() method is called in the afterPropertiesSet method of the AbstractHandlerMethodMapping class. Looking at the name of the initHandlerMethods() method, you can see that it actually does the initialization work.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/**
    * Scan beans in the ApplicationContext, detect and register handler methods.
    * @see #getCandidateBeanNames()
    * @see #processCandidateBean
    * @see #handlerMethodsInitialized
    */
protected void initHandlerMethods() {
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

In the initHandlerMethods() method, the getCandidateBeanNames method first gets the names of all the beans in the Spring container. The bean name is passed through the loop to the processCandidateBean(beanName) method.

The processCandidateBean(beanName) method does three main things.

  • Find the type corresponding to the bean by its name.
  • Filter out beans that do not match the criteria by the isHandler method. isHandler method is a template method and the specific logic is implemented in the subclass RequestMappingHandlerMapping. isHandler method will only select beans that contain @Controller and @RequestMapping annotations.
  • The corresponding mapping between request request and handler is established by the detectHandlerMethods method.

The detectHandlerMethods method does two main things.

  1. use the getMappingForMethod method to find the method with the @RequestMapping annotation through the handler. In AbstractHandlerMethodMapping class, getMappingForMethod method in just an abstract method, the specific implementation is in the subclass RequestMappingHandlerMapping class, to achieve the specific business logic.

  2. use the registerHandlerMethod method to register the found method. The so-called registration is actually the found HandlerMothed into a Map. If the same HandlerMothed is registered for a second time, an exception will be thrown.

Handler lookup

In the AbstractHandlerMethodMapping class, the template method getHandlerInternal of the parent class AbstractHandlerMapping is overridden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
    * Look up a handler method for the given request.
    */
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = initLookupPath(request);
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

In the getHandlerInternal method, there are three main events that are done.

  • In the initLookupPath method, a request is converted into a url.
  • Then use the lookupHandlerMethod method to find the corresponding HandlerMethod by using the two parameters request and lookupPath.
  • In the case of finding a HandlerMethod object, the createWithResolvedBean method of the HandlerMethod will be called. This method determines whether this HandlerMethod object. If it is of type String, it is just a String. If it is of type String then it is just the name of a bean and the bean will be found in the Spring container based on the name of the bean.

RequestMappingInfoHandlerMapping

The RequestMappingInfoHandlerMapping class mainly overrides the getMatchingMapping method of the parent class. getMatchingMapping method returns a RequestMappingInfo object that matches various RequestCondition based on the current request. RequestMappingInfo object that matches various RequestCondition.

SpringMVC will get the corresponding Handler based on this RequestMappingInfo object.

RequestMappingHandlerMapping

Spring MVC container in the initialization of the HandlerMapping type components, the final initialization of the AbstractHandlerMethodMapping series of components, is RequestMappingHandlerMapping.

RequestMappingHandlerMapping mainly overrides the three methods of the parent class.

  1. afterPropertiesSet

    The initialization method of the parent class is overridden and an object of type BuilderConfiguration is created in the afterPropertiesSet method. Then for the BuilderConfiguration object, the properties are set.

  2. isHandler

    The main purpose is to determine what type of Handler to get, and to play a filtering role on the Handler, only take the @Controller and @RequestMapping two annotated types of Handler.

  3. getMappingForMethod

The getMappingForMethod method is mainly used to create the corresponding RequestMappingInfo object by method. The program first gets the RequestMapping annotation from the method object. From the RequestMapping annotation information, it creates “PathMapping”, “HeaderMapping”, “RequestParameterMapping”, “MIMEMapping”, “MIMEMapping”, “MIMEMapping”, and “MIMEMapping”. “MIME matchable”, “MIME matchable”, “request method matchable”, and other RequestCondition interface instances. Finally these RequestCondition interface instances are combined to create a RequestMappingInfo object and SpringMVC registers the request and Handler mapping relationship based on the RequestMappingInfo object.

The RequestMappingInfo object implements the RequestCondition interface. The interface RequestCondition is Spring MVC’s conceptual modeling of a request matching condition.

AbstractUrlHandlerMapping series

AbstractUrlHandlerMapping series can be seen from the name, it mainly deals with the relationship between url and handler.

The AbstractUrlHandlerMapping class firstly puts the mapping relationship between url and handler into a Map, and then gets the corresponding handler by url.

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping class, like AbstractHandlerMethodMapping class, also inherits AbstractHandlerMapping abstract class. All url and HandlerMapping related subclasses are inherited from the AbstractUrlHandlerMapping parent class.

AbstractUrlHandlerMapping also implements the MatchableHandlerMapping interface, which defines a match method for matching.

HandlerMap registration

In AbstractUrlHandlerMapping class, the method registerHandler is specifically responsible for registering the handler.

Handler registration is actually storing the url and the corresponding handler in the hash map called handlerMap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
    * Register the specified handler for the given URL path.
    * @param urlPath the URL the bean should be mapped to
    * @param handler the handler instance or handler bean name String
    * (a bean name will automatically be resolved into the corresponding handler bean)
    * @throws BeansException if the handler couldn't be registered
    * @throws IllegalStateException if there is a conflicting handler registered
    */
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    // Eagerly resolve handler if referencing singleton via name.
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        ApplicationContext applicationContext = obtainApplicationContext();
        if (applicationContext.isSingleton(handlerName)) {
            resolvedHandler = applicationContext.getBean(handlerName);
        }
    }

    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
        if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException(
                    "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                    "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
        }
    }
    else {
        if (urlPath.equals("/")) {
            if (logger.isTraceEnabled()) {
                logger.trace("Root mapping to " + getHandlerDescription(handler));
            }
            setRootHandler(resolvedHandler);
        }
        else if (urlPath.equals("/*")) {
            if (logger.isTraceEnabled()) {
                logger.trace("Default mapping to " + getHandlerDescription(handler));
            }
            setDefaultHandler(resolvedHandler);
        }
        else {
            this.handlerMap.put(urlPath, resolvedHandler);
            if (getPatternParser() != null) {
                this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
            }
        }
    }
}

In the registerHandler method, there are 4 main things done.

  1. if the handler is not lazy loaded and the handler is of string type. At this point the handler as a BeanName, in the Spring container to get the bean object of the handler.
  2. the urlPath is verified, if a urlPath corresponds to more than one different handler, the code will throw an exception.
  3. the special urlPath is handled separately, the setRootHandler method and setDefaultHandler method are called for “/”, “/*” respectively for special handling.
  4. record the correspondence between url and handler in handlerMap. The code this.handlerMap.put(urlPath, resolvedHandler) stores the url and handler into the hash hash by key-value pairs.

Handler lookup

The AbstractUrlHandlerMapping class specifies how to get the corresponding handler from a url.

The AbstractUrlHandlerMapping class overrides the getHandlerInternal method. The logic to get the handler from the url is written in the getHandlerInternal method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
    * Look up a handler for the URL path of the given request.
    * @param request current HTTP request
    * @return the handler instance, or {@code null} if none found
    */
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = initLookupPath(request);
    Object handler;
    if (usesPathPatterns()) {
        RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
        handler = lookupHandler(path, lookupPath, request);
    }
    else {
        handler = lookupHandler(lookupPath, request);
    }
    if (handler == null) {
        // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if (StringUtils.matchesCharacter(lookupPath, '/')) {
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = obtainApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    return handler;
}

In the getHandlerInternal method, the program will first get a url based on the request, and then use the lookupHandler method to get the corresponding Handler.

lookupHandler method

The lookupHandler method is a method to find the corresponding Handler by url. lookupHandler is firstly retrieved directly from the HandlerMap. If the Handler is found, it will be returned directly.

If you can’t get it directly from the handlerMap, you will use the PathPattern to match the pattern. If a url matches more than one PathPattern, the PathPattern will be sorted and the best one will be taken.

If the Handler is of type String, then the Handler is the name of the Handle bean. Then find the corresponding bean in the Spring container based on this BeanName.

After getting the Handler, the Handler is validated using validateHandler. validateHandler is a template method, which is mainly left to subclasses to extend the implementation.

Finally, the buildPathExposingHandler method is used to add some interceptors to this Handler.

buildPathExposingHandler

The buildPathExposingHandler method mainly registers two internal interceptors to the Handler. They are the PathExposingHandlerInterceptor and the UriTemplateVariablesHandlerInterceptor interceptor.

AbstractDetectingUrlHandlerMapping

In AbstractDetectingUrlHandlerMapping class, the main thing is to override the initApplicationContext() method of the parent class. In the initApplicationContext() method, the detectHandlers() method is called.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Register all handlers found in the current ApplicationContext.
* <p>The actual URL determination for a handler is up to the concrete
* {@link #determineUrlsForHandler(String)} implementation. A bean for
* which no such URLs could be determined is simply not considered a handler.
* @throws org.springframework.beans.BeansException if the handler couldn't be registered
* @see #determineUrlsForHandler(String)
*/
protected void detectHandlers() throws BeansException {
    ApplicationContext applicationContext = obtainApplicationContext();
    String[] beanNames = (this.detectHandlersInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
            applicationContext.getBeanNamesForType(Object.class));

    // Take any bean name that we can determine URLs for.
    for (String beanName : beanNames) {
        String[] urls = determineUrlsForHandler(beanName);
        if (!ObjectUtils.isEmpty(urls)) {
            // URL paths found: Let's consider it a handler.
            registerHandler(urls, beanName);
        }
    }

    if (mappingsLogger.isDebugEnabled()) {
        mappingsLogger.debug(formatMappingName() + " " + getHandlerMap());
    }
    else if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
        logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
    }
}

In the method detectHandlers() is called, the following steps are done.

  1. Get the names of all beans in the Spring container.
  2. loop through all the bean names and call the determineUrlsForHandler method for each beanName to get the url corresponding to that beanName.
  3. then call the registerHandler(urls, beanName) method of the parent class to register the url and the handler.

The determineUrlsForHandler(beanName) method in the AbstractDetectingUrlHandlerMapping class is just an abstract method, which is left to the subclasses for concrete implementation.

BeanNameUrlHandlerMapping

The Spring MVC container initializes the BeanNameUrlHandlerMapping component when it initializes the components of the HandlerMapping type by default when it initializes the components of the AbstractUrlHandlerMapping series.

The BeanNameUrlHandlerMapping class inherits from the parent class AbstractDetectingUrlHandlerMapping, and the child class mainly overrides the determineUrlsForHandler method. determineUrlsForHandler method Finally, the name of the bean and the corresponding bean object will be registered to the handlerMap HashMap object.

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping class inherits from the parent class AbstractUrlHandlerMapping, which has a Map<String, Object> type urlMap property. SimpleUrlHandlerMapping class will iterate through the urlMap in the registerHandlers(Map<String, Object> urlMap) method and then call AbstractUrlHandlerMapping parent class registerHandler(String urlPath, Object handler) method to register the url and HandlerMapping.

In addition to the urlMap property of type Map<String, Object>. the SimpleUrlHandlerMapping class also provides the use of Properties for url registration. With the setMappings(Properties mappings) method, SimpleUrlHandlerMapping will merge the mappings into the urlMap property. Then the registration logic of urlMap properties is carried out.

Reference https://www.cnblogs.com/YaoxTao/p/16583805.html