3 HandlerMapping

图3-1

在第2章,DipatcherServlet中使用 HandlerMapping 得到执行链HandlerExecutionChain,然后就再也没有 HandlerMapping 的事了。也就是说SpringMVC就是靠 HandlerMapping 通过入参 HttpServletRequest 判断应该调用哪个方法、或者返回哪个文件,所以就让我们从这里入手。

3.1 HandlerExecutionChain 对象

这个对象很简单,没有继承,没有子类。DipatcherServlet中用到了,HandlerMapping 也用到了,所以必须提一下。其中Object的handler就是具体处理器,interceptors会在handler执行前后以及整个请求处理完后调用。

HandlerInterceptor 是什么这里简单介绍一下,它是一个接口,还记得在第2章讲执行链调用预处理方法、后处理方法和完成触发方法吗?调用的就是这个接口的对应方法。MappedInterceptor 是 HandlerInterceptor 接口的继承接口,它的作用包装一个 HandlerInterceptor 并且可以设置上URI规则,允许调用接口方法前先判断是否符合规则。其他的 HandlerInterceptor 实现类目前就先不介绍了。

其中前三个主要方法各有特色,我就分别来讲讲。

/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

预处理(前处理)方法大家请看,for循环是从小到大的,还记录了拦截器的下标,当有一个拦截器返回false时,会调用完成触发方法 triggerAfterCompletion。

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

后处理方法和前处理方法区别在for循环是从大往小的。

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
        throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

完成触发方法更奇葩,for循环从大往小不说,还从记录的下标开始。什么意思呢?就是说如果执行到第index+1个预处理方法返回了false,就从第index个拦截器倒着往回执行完成触发方法。

最后一个方法是针对Servlet3.0标准中的异步处理设置的,这里就先不讲了。

3.2 HandlerMapping接口分析

HandlerMapping 接口非常简单,就一个方法,从 HttpServletRequest 获取一个执行链HandlerExecutionChain,如果没有映射上的handler,就应该返回null。


/**
 * Interface to be implemented by objects that define a mapping between
 * requests and handler objects.
 *
 * <p>This class can be implemented by application developers, although this is not
 * necessary, as {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
 * and {@link org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping}
 * are included in the framework. The former is the default if no
 * HandlerMapping bean is registered in the application context.
 *
 * <p>HandlerMapping implementations can support mapped interceptors but do not
 * have to. A handler will always be wrapped in a {@link HandlerExecutionChain}
 * instance, optionally accompanied by some {@link HandlerInterceptor} instances.
 * The DispatcherServlet will first call each HandlerInterceptor's
 * {@code preHandle} method in the given order, finally invoking the handler
 * itself if all {@code preHandle} methods have returned {@code true}.
 *
 * <p>The ability to parameterize this mapping is a powerful and unusual
 * capability of this MVC framework. For example, it is possible to write
 * a custom mapping based on session state, cookie state or many other
 * variables. No other MVC framework seems to be equally flexible.
 *
 * <p>Note: Implementations can implement the {@link org.springframework.core.Ordered}
 * interface to be able to specify a sorting order and thus a priority for getting
 * applied by DispatcherServlet. Non-Ordered instances get treated as lowest priority.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.core.Ordered
 * @see org.springframework.web.servlet.handler.AbstractHandlerMapping
 * @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
 * @see org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
 */
public interface HandlerMapping {

    /**
     * Name of the {@link HttpServletRequest} attribute that contains the path
     * within the handler mapping, in case of a pattern match, or the full
     * relevant URI (typically within the DispatcherServlet's mapping) else.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations. URL-based HandlerMappings will
     * typically support it, but handlers should not necessarily expect
     * this request attribute to be present in all scenarios.
     */
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

    /**
     * Name of the {@link HttpServletRequest} attribute that contains the
     * best matching pattern within the handler mapping.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations. URL-based HandlerMappings will
     * typically support it, but handlers should not necessarily expect
     * this request attribute to be present in all scenarios.
     */
    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

    /**
     * Name of the boolean {@link HttpServletRequest} attribute that indicates
     * whether type-level mappings should be inspected.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations.
     */
    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

    /**
     * Name of the {@link HttpServletRequest} attribute that contains the URI
     * templates map, mapping variable names to values.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations. URL-based HandlerMappings will
     * typically support it, but handlers should not necessarily expect
     * this request attribute to be present in all scenarios.
     */
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

    /**
     * Name of the {@link HttpServletRequest} attribute that contains a map with
     * URI matrix variables.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations and may also not be present depending on
     * whether the HandlerMapping is configured to keep matrix variable content
     * in the request URI.
     */
    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

    /**
     * Name of the {@link HttpServletRequest} attribute that contains the set of
     * producible MediaTypes applicable to the mapped handler.
     * <p>Note: This attribute is not required to be supported by all
     * HandlerMapping implementations. Handlers should not necessarily expect
     * this request attribute to be present in all scenarios.
     */
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    /**
     * Return a handler and any interceptors for this request. The choice may be made
     * on request URL, session state, or any factor the implementing class chooses.
     * <p>The returned HandlerExecutionChain contains a handler Object, rather than
     * even a tag interface, so that handlers are not constrained in any way.
     * For example, a HandlerAdapter could be written to allow another framework's
     * handler objects to be used.
     * <p>Returns {@code null} if no match was found. This is not an error.
     * The DispatcherServlet will query all registered HandlerMapping beans to find
     * a match, and only decide there is an error if none can find a handler.
     * @param request current HTTP request
     * @return a HandlerExecutionChain instance containing handler object and
     * any interceptors, or {@code null} if no mapping found
     * @throws Exception if there is an internal error
     */
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

接口虽简单,但是要注意其中的一些常量。

  • PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE: 这个常量会被放到request的属性中,用来设置SpringMVC用的URI地址。
  • BEST_MATCHING_PATTERN_ATTRIBUTE: 这个常量会被放到request的属性中,用来设置匹配到的最合适的URI。
  • INTROSPECT_TYPE_LEVEL_MAPPING: 这个常量目前来说没用。
  • URI_TEMPLATE_VARIABLES_ATTRIBUTE: 这个常量会被放到request的属性中,用于存放临时变量,在使用restfull风格的uri时会用到。
  • MATRIX_VARIABLES_ATTRIBUTE: URI标准中有一种参数叫做矩阵参数,例如aaa/bbb;a=1;b=c。这个常量会被放到request的属性中,当有矩阵参数时,矩阵参数的解析结果将放到这里。
  • PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE:这个常量会被放到request的属性中,会将映射上的处理器能产生的media type类型放到这里。

3.3 AbstractHandlerMapping 解析

在图3-1上,我们看到 HandlerMapping 的唯一直接实现类是 AbstractHandlerMapping ,那我们就从 AbstractHandlerMapping 开始看吧。

3.3.1 AbstractHandlerMapping 初始化解析

别看这个类属性那么多,还继承了你不知道的类。根据之前的经验,初始化部分,只要你看到这个类里有重写父类的方法,带有init类似字段,那这个方法保准是初始化方法。

按照这个道理,我们顺理成章地找到了下面这些方法。

/**
 * Initializes the interceptors.
 * @see #extendInterceptors(java.util.List)
 * @see #initInterceptors()
 */
@Override
protected void initApplicationContext() throws BeansException {
    extendInterceptors(this.interceptors);
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

/**
 * Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
 * <p>This is called in addition to any {@link MappedInterceptor}s that may have been provided
 * via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
 * from the current context and its ancestors. Subclasses can override and refine this policy.
 * @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
 */
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    getApplicationContext(), MappedInterceptor.class, true, false).values());
}

/**
 * Initialize the specified interceptors, checking for {@link MappedInterceptor}s and
 * adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor}s if necessary.
 * @see #setInterceptors
 * @see #adaptInterceptor
 */
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

/**
 * Adapt the given interceptor object to the {@link HandlerInterceptor} interface.
 * <p>By default, the supported interceptor types are {@link HandlerInterceptor}
 * and {@link WebRequestInterceptor}. Each given {@link WebRequestInterceptor}
 * will be wrapped in a {@link WebRequestHandlerInterceptorAdapter}.
 * Can be overridden in subclasses.
 * @param interceptor the specified interceptor object
 * @return the interceptor wrapped as HandlerInterceptor
 * @see org.springframework.web.servlet.HandlerInterceptor
 * @see org.springframework.web.context.request.WebRequestInterceptor
 * @see WebRequestHandlerInterceptorAdapter
 */
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
        return (HandlerInterceptor) interceptor;
    }
    else if (interceptor instanceof WebRequestInterceptor) {
        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else {
        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
}

这些方法做了一件事,就是在web应用上下文创建完成后,从上下文中找到 MappedInterceptor 并且和 自身属性 interceptors 做融合,设置到属性 adaptedInterceptors 中。

3.3.2 AbstractHandlerMapping 执行请求分析

AbstractHandlerMapping 是一个抽象类,所以它应该还会把具体的获取handler交给它的子类。那他在这里会做什么呢?来看代码吧

/**
 * 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
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 = getApplicationContext().getBean(handlerName);
    }
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

/**
 * Look up a handler for the given request, returning {@code null} if no
 * specific one is found. This method is called by {@link #getHandler};
 * a {@code null} return value will lead to the default handler, if one is set.
 * <p>On CORS pre-flight requests this method should return a match not for
 * the pre-flight request but for the expected actual request based on the URL
 * path, the HTTP methods from the "Access-Control-Request-Method" header, and
 * the headers from the "Access-Control-Request-Headers" header thus allowing
 * the CORS configuration to be obtained via {@link #getCorsConfigurations},
 * <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
 * combining a handler object with dynamically determined interceptors.
 * Statically specified interceptors will get merged into such an existing chain.
 * @param request current HTTP request
 * @return the corresponding handler instance, or {@code null} if none found
 * @throws Exception if there is an internal error
 */
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

/**
 * Build a {@link HandlerExecutionChain} for the given handler, including
 * applicable interceptors.
 * <p>The default implementation builds a standard {@link HandlerExecutionChain}
 * with the given handler, the handler mapping's common interceptors, and any
 * {@link MappedInterceptor}s matching to the current request URL. Interceptors
 * are added in the order they were registered. Subclasses may override this
 * in order to extend/rearrange the list of interceptors.
 * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
 * pre-built {@link HandlerExecutionChain}. This method should handle those
 * two cases explicitly, either building a new {@link HandlerExecutionChain}
 * or extending the existing chain.
 * <p>For simply adding an interceptor in a custom subclass, consider calling
 * {@code super.getHandlerExecutionChain(handler, request)} and invoking
 * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
 * @param handler the resolved handler instance (never {@code null})
 * @param request current HTTP request
 * @return the HandlerExecutionChain (never {@code null})
 * @see #getAdaptedInterceptors()
 */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

以上是我整理出来的相关方法。

在 getHandler 方法中,我们看到第10行开头就将获取handler的任务交给了抽象方法,待子类实现。

其次,第12行代码告诉我们,我们可以在初始化时向 HandlerMapping 设置一个默认handler,这个以后再讲。

在第18行到第21行代码中,判断如果handler是String类型的,则会从web应用上下文中获取对应的bean。

在第22行代码中,调用了 getHandlerExecutionChain 方法,还记得前一小节中讲的 MappedInterceptor 这个类吗?它就是在这里使用的。也就是说,AbstractHandlerMapping 拥有上下文中所有的 HandlerInterceptor ,但是在处理一次请求时,会只让符合规则的 HandlerInterceptor 参与拦截。

第23行到第28行代码是处理跨域请求的,以后讲到跨域时,我们再来详细看看。

3.3.3 AbstractHandlerMapping 小结

AbstractHandlerMapping 抽象类还是比较简单的。通过第2章和第3章,我们发现DispatcherServlet中拿到的时执行链 HandlerExecutionChain,会先调用执行链的预处理方法,再通过适配器调用执行链里的handler,再调用后处理方法,等处理完视图渲染后最后调用完成触发方法。为什么有 AbstractHandlerMapping 抽象类?我们发现他负责了 HandlerInterceptor 的初始化和构造成为 HandlerExecutionChain。而更复杂的handler生成就交给了子类。

3.4 AbstractUrlHandlerMapping 解析

在图3-1中,我们发现 AbstractHandlerMapping 只有两个子类,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。咱们就从最简单的 AbstractUrlHandlerMapping 抽象类开始吧。

3.4.1 AbstractUrlHandlerMapping 初始化分析

纵览 AbstractUrlHandlerMapping 类,发现这个类并没有初始化方法,虽是抽象类,但是并没有强制需要子类重写的方法,也是一个奇葩了。但实际上并不奇葩,来看一下我总结的初始化方法。

/**
 * 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;
        if (getApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = getApplicationContext().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.isInfoEnabled()) {
                logger.info("Root mapping to " + getHandlerDescription(handler));
            }
            setRootHandler(resolvedHandler);
        }
        else if (urlPath.equals("/*")) {
            if (logger.isInfoEnabled()) {
                logger.info("Default mapping to " + getHandlerDescription(handler));
            }
            setDefaultHandler(resolvedHandler);
        }
        else {
            this.handlerMap.put(urlPath, resolvedHandler);
            if (logger.isInfoEnabled()) {
                logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
            }
        }
    }
}

这个类的作用是保存URI和handler的对应关系,在父类调用重写的 getHandlerInternal 方法时,通过request中的URI查找最合适的handler。那URI和handler关系从哪来?当然是通过注册得来的。AbstractUrlHandlerMapping 应该是允许子类在初始化时,调用 registerHandler 方法来注册handler。

3.4.2 AbstractUrlHandlerMapping 处理请求分析

处理请求那是相当简单,前一小节不是注册了URI和handler的映射关系吗?处理请求就是直接取映射值,映射值好取吗?不带正则的话当然好取,带正则后那就稍微复杂一点。直接看代码吧。

/**
 * 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
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    Object 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 ("/".equals(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 = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    return handler;
}


/**
 * Look up a handler instance for the given URL path.
 * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
 * and various Ant-style pattern matches, e.g. a registered "/t*" matches
 * both "/test" and "/team". For details, see the AntPathMatcher class.
 * <p>Looks for the most exact pattern, where most exact is defined as
 * the longest path pattern.
 * @param urlPath URL the bean is mapped to
 * @param request current HTTP request (to expose the path within the mapping to)
 * @return the associated handler instance, or {@code null} if not found
 * @see #exposePathWithinMapping
 * @see org.springframework.util.AntPathMatcher
 */
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    // Direct match?
    Object handler = this.handlerMap.get(urlPath);
    if (handler != null) {
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }
    // Pattern match?
    List<String> matchingPatterns = new ArrayList<String>();
    for (String registeredPattern : this.handlerMap.keySet()) {
        if (getPathMatcher().match(registeredPattern, urlPath)) {
            matchingPatterns.add(registeredPattern);
        }
        else if (useTrailingSlashMatch()) {
            if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
                matchingPatterns.add(registeredPattern +"/");
            }
        }
    }
    String bestPatternMatch = null;
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if (!matchingPatterns.isEmpty()) {
        Collections.sort(matchingPatterns, patternComparator);
        if (logger.isDebugEnabled()) {
            logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
        }
        bestPatternMatch = matchingPatterns.get(0);
    }
    if (bestPatternMatch != null) {
        handler = this.handlerMap.get(bestPatternMatch);
        if (handler == null) {
            Assert.isTrue(bestPatternMatch.endsWith("/"));
            handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }
        validateHandler(handler, request);
        String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
        // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
        // for all of them
        Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
        for (String matchingPattern : matchingPatterns) {
            if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
                Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
                uriTemplateVariables.putAll(decodedVars);
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
        }
        return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
    }
    // No handler found...
    return null;
}

3.4.3 AbstractUrlHandlerMapping 小结

AbstractUrlMapping 的一个子类叫 AbstractUrlHandlerMapping,另一个子类叫 AbstractHandlerMethodMapping。有没有发现猫腻?稍微有点常识的人都会发现 第一个子类是URI与handler对象的映射,第二个子类是handler对象上方法与URI的映射。

为什么这么讲?AbstractUrlHandlerMapping 只存了URI和handler的映射关系,没存URI到方法的关系,那你要是写一个普通类当做handler,那SpringMVC怎么知道要调用handler的哪个方法呢?所以像这种映射的handler必须是继承SpringMVC中特有的类或接口,像开发者常用的有Controllor接口及其实现子类、Servlet实现类等等。

注册URI和handler映射也有讲究,不信来看看它的实现类吧。

3.5 SimpleUrlHandlerMapping 解析

前面讲了那么多接口,抽象类,我想大家也都挺晕的,这次终于到了一个我们常见的实现类了。这个类超级简单,3.4节讲到 AbstractUrlHandlerMapping 留了一个 registerHandler 方法吗?来看看这个类是怎么用的。

/**
 * Calls the {@link #registerHandlers} method in addition to the
 * superclass's initialization.
 */
@Override
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    registerHandlers(this.urlMap);
}

/**
 * Register all handlers specified in the URL map for the corresponding paths.
 * @param urlMap Map with URL paths as keys and handler beans or bean names as values
 * @throws BeansException if a handler couldn't be registered
 * @throws IllegalStateException if there is a conflicting handler registered
 */
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
            String url = entry.getKey();
            Object handler = entry.getValue();
            // Prepend with slash if not already present.
            if (!url.startsWith("/")) {
                url = "/" + url;
            }
            // Remove whitespace from handler bean name.
            if (handler instanceof String) {
                handler = ((String) handler).trim();
            }
            registerHandler(url, handler);
        }
    }
}

简不简单?只要你在xml配置文件中配置了这个bean,然后给它的 urlMap 属性中设置映射,如果映射的Key忘记带/了,他会帮你自动补上,映射的Value可以是ref引用bean,也可以是bean的name,都随你配置。

这里我给出一个xml配置的demo:

<bean id="simpleController" class="com.gavinzh.learn.web.simple.controller.SimpleController"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="urlMap">
        <map>
            <entry key="/simple" value-ref="simpleController"/>
        </map>
    </property>
    <property name="order" value="0"/>
</bean>

3.6 AbstractDetectingUrlHandlerMapping

前面的 SimpleUrlHandlerMapping 的确很好用,在xml中的 urlMap 就能看到项目的所有映射。但是,有一个问题会在业务快速增加时困扰你。业务增加了,handler写的越来越多了,不仅得写bean配置,还得在 urlMap 中配置一遍,你说头疼不头疼?

SpringMVC的开发人员可能在某一天的早上坐地铁的时候想到了这个问题,那怎么解决呢?方法很简单,我写一个抽象类,功能和 SimpleUrlHandlerMapping 一样,在初始化时加载handler。那handler配置从哪来?想从哪来从哪来呗,我留一个抽象方法等子类实现不就对了?

是的 AbstractDetectingUrlHandlerMapping 就是干这破事的。来看看我总结的方法吧:

/**
 * Calls the {@link #detectHandlers()} method in addition to the
 * superclass's initialization.
 */
@Override
public void initApplicationContext() throws ApplicationContextException {
    super.initApplicationContext();
    detectHandlers();
}
/**
 * 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 {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
    }
    String[] beanNames = (this.detectHandlersInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().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);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
            }
        }
    }
}
/**
 * Determine the URLs for the given handler bean.
 * @param beanName the name of the candidate bean
 * @return the URLs determined for the bean,
 * or {@code null} or an empty array if none
 */
protected abstract String[] determineUrlsForHandler(String beanName);

啪啪啪,和 SimpleUrlHandlerMapping 是不是一样?都是在初始化时做handler的注册?。

3.7 BeanNameUrlHandlerMapping 分析

AbstractDetectingUrlHandlerMapping 竟然是抽象类?还要子类通过beanName找到URI?太过分了!!!

SpringMVC的开发人员估计怕被打,于是他们自己写了一个简单的实现类,有多简单?beanName就是URI,别名也是URI,这样简单多了吧?

来看看源码吧

/**
 * Checks name and aliases of the given bean for URLs, starting with "/".
 */
@Override
protected String[] determineUrlsForHandler(String beanName) {
    List<String> urls = new ArrayList<String>();
    if (beanName.startsWith("/")) {
        urls.add(beanName);
    }
    String[] aliases = getApplicationContext().getAliases(beanName);
    for (String alias : aliases) {
        if (alias.startsWith("/")) {
            urls.add(alias);
        }
    }
    return StringUtils.toStringArray(urls);
}

我来展示一个demo:

<bean name="/named" id="/named" class="com.gavinzh.learn.web.simple.controller.NamedController"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" p:order="0"/>

3.8 AbstractHandlerMethodMapping 分析

前面我们分析了 AbstractUrlHandlerMapping 及其实现类,知道了那边用的时URI和handler映射,handler必须是继承或者实现某个类或接口才能用。

麻不麻烦?麻烦!一个URI就得一个类,你说麻烦不麻烦?SpringMVC的开发人员头估计都快被打爆了,怎么办怎么办?一个方法就对应一个URI!灵感就这么产生了。

3.8.1 HandlerMethod 和 MappingRegistry

想要了解针对方法的 HandlerMapping,就得先了解这两个类。

简单了解一下就行。先看 HandlerMethod 这个类,这个包含了一个bean对象、创建bean的工厂、bean的类型、方法、桥方法、方法入参类型、另一个HandlerMethod。这个对象精确到了方法,也就是说你拿着这个对象,再加入参,就能反射调用方法了。

再来看看 MappingRegistry,前面虽说 HandlerMethod 精确到了方法,但实际上并没有和URI有任何关系,MappingRegistry 就是实现URI和HandlerMethod的映射的。就有人跟我抬杠,说这个类中map的Key是T泛型,不是URI啊,这么来理解吧,一个请求request过来,携带了什么信息?URI,METHOD等等等等,是的这个T泛型之后可能指的是这一系列的组合,我们提的URI也就是代表一个请求的特征。

3.8.2 AbstractHandlerMethodMapping 初始化分析

在Spring中,对象都是有生命周期的,所以我们讲了这么多类,都是从初始化开始的。AbstractHandlerMapping 类并没有给子类留一个初始化抽象方法,那咱们就找找别的初始化点吧。下面是我找到的初始化点:

/**
 * Detects handler methods at initialization.
 */
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

/**
 * Scan beans in the ApplicationContext, detect and register handler methods.
 * @see #isHandler(Class)
 * @see #getMappingForMethod(Method, Class)
 * @see #handlerMethodsInitialized(Map)
 */
protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }
    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));
    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            Class<?> beanType = null;
            try {
                beanType = getApplicationContext().getType(beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                }
            }
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

/**
 * Look for handler methods in a handler.
 * @param handler the bean name of a handler or a handler instance
 */
protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType = (handler instanceof String ?
            getApplicationContext().getType((String) handler) : handler.getClass());
    final Class<?> userType = ClassUtils.getUserClass(handlerType);
    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
            new MethodIntrospector.MetadataLookup<T>() {
                @Override
                public T inspect(Method method) {
                    return getMappingForMethod(method, userType);
                }
            });
    if (logger.isDebugEnabled()) {
        logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
    }
    for (Map.Entry<Method, T> entry : methods.entrySet()) {
        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
        T mapping = entry.getValue();
        registerHandlerMethod(handler, invocableMethod, mapping);
    }
}

/**
 * Whether the given type is a handler with handler methods.
 * @param beanType the type of the bean being checked
 * @return "true" if this a handler type, "false" otherwise.
 */
protected abstract boolean isHandler(Class<?> beanType);


/**
 * Provide the mapping for a handler method. A method for which no
 * mapping can be provided is not a handler method.
 * @param method the method to provide a mapping for
 * @param handlerType the handler type, possibly a sub-type of the method's
 * declaring class
 * @return the mapping, or {@code null} if the method is not mapped
 */
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

实际上还会调用别的初始化方法,但实际上并没有用到,所以就不展示了。

在代码第19行到第38行,会从web应用上下文中获取所有对象,然后 调用 isHandler 方法判断是不是handler,如果是,就调用 detectHandlerMethods 方法选择出可以处理请求的method,并注册到 MappingRegistry 中。

在代码第51行到第56行,又调用了抽象方法 getMappingForMethod 获取可以处理请求的method并包装为泛型T。

在代码第63行,会把泛型T、handler和method注册到 MappingRegistry。

哦了,别的代码不用看了,子类最起码要判断bean是不是handler,还得从方法生成泛型T。

3.8.3 AbstractHandlerMethodMapping 处理请求分析

在3.3节 AbstractHandlerMapping 分析中,我们知道 HandlerInterceptor 由 AbstractHandlerMapping 添加并管理。留给子类的方法是 getHandlerInternal,用于获取真正的handler。

通过 3.8.2 节初始化分析,其实我们已经知道URI等相关信息和方法的映射是存在 MappingRegistry 中的,那么获取handler肯定也就是从那里获取的了。

猜代码不如看代码,看完代码感觉还不如猜代码,来看代码吧:

/**
 * Look up a handler method for the given request.
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
        logger.debug("Looking up handler method for path " + lookupPath);
    }
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        if (logger.isDebugEnabled()) {
            if (handlerMethod != null) {
                logger.debug("Returning handler method [" + handlerMethod + "]");
            }
            else {
                logger.debug("Did not find handler method for [" + lookupPath + "]");
            }
        }
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}


/**
 * Look up the best-matching handler method for the current request.
 * If multiple matches are found, the best match is selected.
 * @param lookupPath mapping lookup path within the current servlet mapping
 * @param request the current request
 * @return the best-matching handler method, or {@code null} if no match
 * @see #handleMatch(Object, String, HttpServletRequest)
 * @see #handleNoMatch(Set, String, HttpServletRequest)
 */
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        Collections.sort(matches, comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
                    lookupPath + "] : " + matches);
        }
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
                        request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
            }
        }
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

可以看到,由于是从泛型T中判断获取 HandlerMethod,复杂度有所提高,不过我们只要知道是从request的各个信息来综合判断就行。

3.9 RequestMappingInfoHandlerMapping 分析

AbstractHandlerMethodMapping 是一个带泛型的抽象类,其泛型代指是请求特征,而 RequestMappingInfoHandlerMapping 则指定了 RequestMappingInfo 类代替泛型。

这里,我们对 RequestMappingInfo 类做一个了解即可。

可以看到,request请求特征在这类中都有体现。

3.10 RequestMappingHandlerMapping 分析

作为 HandlerMapping的终点,也是最重要的一个类,他与我们使用注解@Controllor@RequestMapping密切相关。接下来就来了解一下怎么密切相关了。

3.10.1 RequestMappingHandlerMapping 初始化分析

@Override
public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    super.afterPropertiesSet();
}

我在代码中找到了这个初始化点,在这个初始化点里,我们发现对于 RequestMappingInfo 的使用,还得有一个配置文件,才能正常的工作。

3.10.2 RequestMappingHandlerMapping 处理请求分析

在 3.8.2 节 AbstractHandlerMethodMapping 初始化中,我们知道他会从web应用上下文中获取全部bean,然后判断是不是handler,然后注册到 MappingRegistry 中。它留下了两个抽象方法,一个是判断是不是handler,另一个就是通过方法生成request请求规则的方法。抽象方法全部在这里实现了,也很简单,判断是不是handler就是通过注解判断的,生成request的请求规则则是通过注解中的参数来生成。代码如下:

/**
 * {@inheritDoc}
 * Expects a handler to have a type-level @{@link Controller} annotation.
 */
@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

/**
 * Uses method and type-level @{@link RequestMapping} annotations to create
 * the RequestMappingInfo.
 * @return the created RequestMappingInfo, or {@code null} if the method
 * does not have a {@code @RequestMapping} annotation.
 * @see #getCustomMethodCondition(Method)
 * @see #getCustomTypeCondition(Class)
 */
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
    }
    return info;
}

/**
 * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
 * supplying the appropriate custom {@link RequestCondition} depending on whether
 * the supplied {@code annotatedElement} is a class or method.
 * @see #getCustomTypeCondition(Class)
 * @see #getCustomMethodCondition(Method)
 */
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class<?> ?
            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

/**
 * Create a {@link RequestMappingInfo} from the supplied
 * {@link RequestMapping @RequestMapping} annotation, which is either
 * a directly declared annotation, a meta-annotation, or the synthesized
 * result of merging annotation attributes within an annotation hierarchy.
 */
protected RequestMappingInfo createRequestMappingInfo(
        RequestMapping requestMapping, RequestCondition<?> customCondition) {
    return RequestMappingInfo
            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
            .methods(requestMapping.method())
            .params(requestMapping.params())
            .headers(requestMapping.headers())
            .consumes(requestMapping.consumes())
            .produces(requestMapping.produces())
            .mappingName(requestMapping.name())
            .customCondition(customCondition)
            .options(this.config)
            .build();
}

3.11 小结

对于 HandlerMapping 接口,其中只有一个方法就是getHandler,返回的是执行链即包含handler和一组拦截器一个对象。

AbstractHandlerMapping 很棒,帮开发者初始化加载了拦截器,然后为生成的执行链注入符合匹配规则的拦截器,把生成handler的任务交给子类。

然而 AbstractHandlerMapping 有两个子类,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。

AbstractUrlHandlerMapping 抽象类允许子类向String->Object的map中注册URI和对象的handler。旗下有两个最终实现类,SimpleUrlHandlerMapping 实现类需要在xml配置中显式设置URI和handler的对应关系,而 BeanNameUrlHandlerMapping 实现类只需要在xml中配置上,然后将handler配置的name或者id设置成以/开头即可自动注入。

AbstractHandlerMethodMapping 抽象类则是以方法为处理请求核心,为了实现以方法为中心的handler映射,他提供了一个 MappingRegistry 属性,初始化加载所有bean,允许子类判断其是不是handler,然后由子类生成请求规则。它的唯一最终实现类是 RequestMappingHandlerMapping,也就是我们用的@Controllor@RequestMapping注解所对应的映射器。

对于 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping,只需要在xml配置上bean即可,不需要再配置URI和handler的映射规则。

看到这里,你是不是有点恍然大悟的感觉?handler原来是这么来的,我们自己也可以自定义handler,只要配上自己adpater不就可以了?说对了,下一章我们将来分析 HandlerAdpater 及其实现类。

标签: java, springmvc, spring

添加新评论