Spring MVC之HandlerMapping

1. 前言

Spring MVC将请求处理器定义为handler,因为handler可以以很多形式存在,所以Spring并没有限制handler的类型,用Object来表示。然后又因为这个原因,Spring MVC针对不同的handler设计了不同的HandlerAdapter来协调handler处理请求。

那么,Spring是怎么根据请求Request查找到对应的处理器handler的呢?

2. HandlerMapping

根据请求Request查找到对应的处理器handler,这个职责Spring交给了HandlerMapping处理。HandlerMapping是处理器映射器,它的职责就是查找请求对应的处理器。

java 复制代码
public interface HandlerMapping {

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request);
}

HandlerMapping接口定义足够简单,只有一个方法getHandler(),根据请求request对象,查找到可以处理请求的处理器handler。又因为handler是可以支持自定义拦截器HandlerInterceptor的,所以Spring将handler和一堆拦截器统一封装为HandlerExecutionChain对象。

3. HandlerExecutionChain

因为除了handler本身处理业务逻辑之外,Spring MVC还支持给handler注册拦截器HandlerInterceptor,所以Spring MVC把它们封装成了HandlerExecutionChain对象。

java 复制代码
public class HandlerExecutionChain {
    // 目标处理器
    private final Object handler;
    // 一堆拦截器
    @Nullable
    private HandlerInterceptor[] interceptors;
    @Nullable
    private List<HandlerInterceptor> interceptorList;
    // 拦截器索引
    private int interceptorIndex = -1;
}

HandlerExecutionChain对象通过HandlerMapping#getHandler()方法构建:

java 复制代码
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 子类获取Handler
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 没有,尝试用默认处理器
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // 如果返回的是字符串,则认定为它的处理器的beanName,去容器加载handler
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }
    // 基于handler和一堆拦截器 构建HandlerExecutionChain
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

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

    if (CorsUtils.isCorsRequest(request)) {
        CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

构建HandlerExecutionChain的流程是:

  1. 调用子类自定义获取handler的逻辑,如果没有尝试使用默认处理器。
  2. 如果handler是字符串类型,则认为它是beanName,通过容器获取。
  3. 基于handler和一堆拦截器构建HandlerExecutionChain。

4. 获取Handler

父类主要是基于子类返回的handler和拦截器构建HandlerExecutionChain,子类获取handler的逻辑才是核心,方法是AbstractHandlerMapping#getHandlerInternal()。HandlerMethod是我们常用的handler,所以我们重点看AbstractHandlerMethodMapping类。

java 复制代码
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 查找HandlerMethod的路径 即解析请求URI
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    this.mappingRegistry.acquireReadLock();// 加读锁
    try {
        // 从MappingRegistry中查找Handler
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        this.mappingRegistry.releaseReadLock();
    }
}

主要做了两件事:

  1. 解析请求的URI,作为查找handler的依据。
  2. 通过MappingRegistry查找handler。

MappingRegistry是映射器注册表,Spring MVC启动时会将扫描到的handler注册到MappingRegistry,查找handler的代码是:

java 复制代码
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // URI直接路径匹配
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // 没有直接匹配到,只能模糊匹配
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        // 匹配到多个,按优先级排序
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            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();
                String uri = request.getRequestURI();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        // 把匹配到的HandlerMethod写入request 最佳匹配handler
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    } else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

步骤:

  1. 先按URI直接路径匹配
  2. URI没有直接匹配到,只能模糊匹配
  3. 如果匹配到多个,按优先级排序
  4. 把匹配到的HandlerMethod写入request属性,记为"最佳匹配handler"

5. HandlerMapping怎么来的

HandlerMapping可以有多个,通过属性handlerMappings保存,它是一个List。查找handler的过程也很简单,遍历所有HandlerMapping挨个查找,只要找到了handler就直接返回。

java 复制代码
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

那HandlerMapping是怎么来的呢???

Spring MVC通过变量detectAllHandlerMappings来控制是否要自动检测所有HandlerMapping,默认是true。

  1. 如果detectAllHandlerMappings为true,则加载容器内所有的HandlerMapping,否则只加载beanName为"handlerMapping"的单个HandlerMapping。
  2. 如果容器内没有可用的HandlerMapping,Spring还有一个兜底方案,就是通过getDefaultStrategies()加载默认的handlerMappings。

默认的handlerMappings是通过classPath下名为DispatcherServlet.properties的配置文件来加载的,如下是Spring MVC的默认配置:

java 复制代码
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
相关推荐
丶小鱼丶几秒前
并发编程之【优雅地结束线程的执行】
java
市场部需要一个软件开发岗位5 分钟前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿9 分钟前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
MZ_ZXD00114 分钟前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
PP东16 分钟前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
ManThink Technology22 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
invicinble26 分钟前
springboot的核心实现机制原理
java·spring boot·后端
人道领域34 分钟前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
大模型玩家七七1 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
CodeToGym1 小时前
【Java 办公自动化】Apache POI 入门:手把手教你实现 Excel 导入与导出
java·apache·excel