springmvc是怎么路由到具体的方法的?

1. 背景

在工作中大量使用到了@RestController 和 @RequestMapping等注解,通过注解定义好接口url,然后请求这个地址就能够调用我们注解下的具体方法,那么springmvc是怎么将url转换为调用具体的bean的方法的呢?

2. 基础概念

  • DispatcherServlet#doDispatch springmvc 处理请求的核心类,包括找到对应方法,调用对应的bean的方法后,进行响应处理。
  • RequestMappingInfo springmvc在启动的时候会将@RequestMapping注解的信息放到RequestMappingInfo中,例如url信息会放到patternsCondition这个属性中。
  • RequestMappingInfoHandlerMapping doDispatch方法会遍历HandlerMapping的所有子类,然后调用各自的getHandlerInternal方法进行寻找匹配的bean的方法。
  • AbstractHandlerMethodMapping RequestMappingInfoHandlerMapping的模板方法,调用lookupHandlerMethod来寻找对应bean和方法。

3. 流程

  1. DispatcherServlet#doDispatch

    调用getHandler获取对应的handler

java 复制代码
mappedHandler = getHandler(processedRequest);
  1. DispatcherServlet#getHandler

    遍历handlerMappings,调用list里的各个实现类的getHandler方法;

java 复制代码
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //初始化的时候会将HandlerMapping 的所有bean放到handlerMappings中
    if (this.handlerMappings != null) {
        //遍历
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            //找不到继续调用下一个mapping的getHandler方法,责任链模式
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

具体的handlerMapping如下:

  1. RequestMappingInfoHandlerMapping.getHandlerInternal

    目前项目中使用的都是@RequestMapping注解,所以我们重点关注RequestMappingInfoHandlerMapping就好,可以看到最终又回去调用父类的getHandlerInternal方法了

java 复制代码
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        //调用父类的方法
        return super.getHandlerInternal(request);
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}
  1. AbstractHandlerMethodMapping#getHandlerInternal

这里可以看到父类的这个方法,没啥核心代码,主要是调用lookupHandlerMethod

java 复制代码
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //提取出url,例如我现在请求的http://127.0.0.1:8080/pre/test,最终拿到的lookupPath为/pre/test
    String lookupPath = initLookupPath(request);
    this.mappingRegistry.acquireReadLock();
    try {
        //根据url和请求获取HandlerMethod
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}
  1. AbstractHandlerMethodMapping#lookupHandlerMethod

    这一段就是比较核心的代码了,这里会根据url先进行查找,然后再根据相关条件进行筛选

java 复制代码
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    //根据url获取到所有的RequestMappingInfo
    //底层维护了一个pathLookup,维护了url和List<RequestMappingInfo> 的映射
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if (directPathMatches != null) {
        //通过条件进行筛选,并把结果放到matches里
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        //兜底遍历一下所有接口
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            //排序一下
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            bestMatch = matches.get(0);
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                for (Match match : matches) {
                    if (match.hasCorsConfig()) {
                        return PREFLIGHT_AMBIGUOUS_MATCH;
                    }
                }
            }
            //不允许存在有两个权重相等的情况
            else {
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.getHandlerMethod().getMethod();
                    Method m2 = secondBestMatch.getHandlerMethod().getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.getHandlerMethod();
    }
    else {
        //返回对应的bean和方法信息
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    }
}
  1. AbstractHandlerMethodMapping#addMatchingMappings
java 复制代码
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        //条件筛选
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            //mappingRegistry将RequestMappingInfo作为key,bean和方法信息作为value,维护了一个map
            matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
        }
    }
}

4. 总结

在实际项目过程中,我们使用到了很多类似这种注解,但是都只是停留在了用的层面,作为一个搞技术的,我们其实需要有一些技术热情,可能在做项目的时候没时间,但是空闲时间来看看原理,也是很有收获的。

相关推荐
XINGTECODE32 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶38 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺43 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
先天牛马圣体1 小时前
如何提升大型AI模型的智能水平
后端
午觉千万别睡过1 小时前
RuoYI分页不准确问题解决
spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
苏-言2 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring