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
相关推荐
CoderYanger3 小时前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第二弹)
java·数据库·spring boot·spring·面试·java-ee·mybatis
CQU_JIAKE5 小时前
11.7[高体】
java·后端·spring
apcipot_rain6 小时前
C++转Java日记(04)
java·开发语言·c++
特立独行的猫A6 小时前
我给女儿写了个打字练习:一边敲键盘,一边把小学英语单词记牢
java·后端·python
憧憬成为java架构高手的小白8 小时前
LangGraph4j 实战(下)
java
天诰8 小时前
SA-K-11 检索会消失吗与四个未解问题
java·服务器·人工智能·python·机器学习·ai编程
这个昵称不容易12 小时前
SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在
java·springboot·毕设
计算机毕设定制辅导-无忧学长12 小时前
《基于微信小程序的居家医疗陪护预约系统设计与实现》
java·spring boot·微信小程序·uniapp·居家医疗陪护预约系统
文丑颜不良啊14 小时前
LeetCode2221~2240题目解析
java·leetcode
秋饼14 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发