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
相关推荐
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ几秒前
idea 删除本地分支后,弹窗 delete tracked brank
java·ide·intellij-idea
言慢行善1 分钟前
idea出现的问题
java·ide·intellij-idea
杨荧11 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的宠物咖啡馆平台
java·开发语言·jvm·vue.js·spring boot·spring cloud·开源
Ling_suu42 分钟前
Spring——单元测试
java·spring·单元测试
ModelBulider44 分钟前
十三、注解配置SpringMVC
java·开发语言·数据库·sql·mysql
苹果酱05671 小时前
C语言 char 字符串 - C语言零基础入门教程
java·开发语言·spring boot·mysql·中间件
csucoderlee1 小时前
eclipse mat leak suspects report和 component report的区别
java·ide·eclipse
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
训山1 小时前
4000字浅谈Java网络编程
java·开发语言·网络
VertexGeek1 小时前
Rust学习(四):作用域、所有权和生命周期:
java·学习·rust