SpringMVC原理(2)-目标方法是怎么被找到的

简介: 目标方法(Handler)是如何被找到的涉及组件:HandlerMapping、MappingRegistry、HandlerExecutionChain

上一篇写到文件上传请求的原理。接下来就是获取Handler的原理了

mappedHandler = getHandler(processedRequest):返回HandlerExecutionChain对象(包含目标方法、拦截器链)

getHandler()

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调用其getHandler()来拿到HandlerExecutionChain对象

5个HandlerMapping

HandlerMapping的作用就是根据请求找到对应的Handler

我们都是在controller接口中然后声明方法来做请求处理的,所以会由这个类处理 RequestMappingHandlerMapping(AbstractHandlerMapping 是它的父类):

private Object defaultHandler;
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 根据当前请求拿到对应的 Handler 
    Object handler = getHandlerInternal(request);
    // 没拿到就给一个默认的 Handler
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved 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 (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

获取处理器对象-getHandlerInternal()

拿到目标方法

最终会来到AbstractHandlerMethodMapping#getHandlerInternal

// 路径帮助器
private UrlPathHelper urlPathHelper = new UrlPathHelper();
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 1、使用路径帮助器拿到请求中的请求路径
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        // 2、通过请求路径拿到 HandlerMethod (核心)
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

根据请求路径找到对应处理器-lookupHandlerMethod()

AbstractHandlerMethodMapping#lookupHandlerMethod

// RequestMappingInfo 集合 k:路径 v:RequestMappingInfo
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
// HandlerMethod 集合 k:RequestMappingInfo v:HandlerMethod
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    // 根据路径匹配到的集合
    List<Match> matches = new ArrayList<>();
    // 1,根据路径找到对应的 RequestMappingInfo 集合
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        // 2,匹配到的集合  这里面有 HandlerMethod 对象
        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()) {
        // 3,从匹配到的集合中拿到第一个
        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)) {
                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 + "}");
            }
        }
        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. getMappingsByUrl():去MappingRegistry注册中心)中根据当前路径找到对应的RequestMappingInfo对象
  2. addMatchingMappings():再根据找到的RequestMappingInfoMappingRegistry注册中心)找HandlerMethod然后封装为Match对象,添加到matches集合中
  3. 如果匹配到了多个,会根据规则拿到最佳匹配的

1、getMappingsByUrl()

MappingRegistry注册中心)中根据当前路径找到对应的RequestMappingInfo对象

// RequestMappingInfo 集合 k:路径 v:RequestMappingInfo
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
public List<T> getMappingsByUrl(String urlPath) {
    return this.urlLookup.get(urlPath);
}

注意PathVariable类型是不会加到这个集合里的,具体逻辑在这里,有兴趣可以去看这两个方法: org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register()、org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getDirectUrls()


注意:这里key是路径,value是RequestMappingInfo对象

2、addMatchingMappings()

再根据找到的RequestMappingInfoMappingRegistry注册中心)找HandlerMethod然后封装为Match对象,添加到matches集合中

// HandlerMethod 集合 k:RequestMappingInfo v:HandlerMethod
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        // 封装为 RequestMappingInfo
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            // 找到的集合,又会封装为 Match 对象
            matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
        }
    }
}
// 封装 RequestMappingInfo
return new RequestMappingInfo(this.name, pathPatterns, patterns,
      methods, params, headers, consumes, produces, custom, this.options);

注意:这里的key是RequestMappingInfo对象,value是HandlerMethod

Match对象

3、得到最佳匹配

最后如果找到了多个,会根据规则拿到最佳匹配的HandlerMethod对象返回给我们

HandlerMethod对象:


目标方法: HandlerMethod对象

获取处理器执行链对象-getHandlerExecutionChain()

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    // 1、封装 HandlerExecutionChain 对象
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                                   (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    // 2、利用路径帮助器拿到当前路径
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
    // 3、遍历所有的拦截器
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            // 3、判断当前拦截器是否拦截此请求
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                // 添加到目标对象中
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

流程:

  1. 先把HandlerMethod封装到HandlerExecutionChain
  2. 利用路径帮助器拿到当前请求路径
  3. 遍历所有的拦截器并判断当前拦截器是否拦截当前请求,拦截的话就添加到我们的HandlerExecutionChain 对象中,然后返回

这两个拦截器是默认的拦截器,不用管

里面就3个东西:处理器、拦截器集合、当前执行到第几个拦截器的索引

到这一步也就拿到了HandlerExecutionChain对象,最后还会有跨域的处理,就把这个对象返回出去了。

跨域放在下一篇说。

public boolean matches(String lookupPath, PathMatcher pathMatcher) {
    PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
    // 当前路径是否在排除路径的列表中
    if (!ObjectUtils.isEmpty(this.excludePatterns)) {
        for (String pattern : this.excludePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return false;
            }
        }
    }
    if (ObjectUtils.isEmpty(this.includePatterns)) {
        return true;
    }
    // 当前路径是否在要拦截器的路径列表中
    for (String pattern : this.includePatterns) {
        if (pathMatcherToUse.match(pattern, lookupPath)) {
            return true;
        }
    }
    return false;
}

总结

HandlerMethod

目标方法。我们通过 @xxxMapping 修饰的Controller中接口就是目标方法。

会通过 RequestMappingHanderMapping 处理器适配器找到它。

会通过 RequestMappingHandlerAdapter 处理器适配器执行。

MappingRegistry

MappingRegistry:映射的注册中心

路径会映射为RequestMappingInfo对象,RequestMappingInfo又和HandlerMethod所映射

所以流程就是先根据路径找到对应的RequestMappingInfo,再根据RequestMappingInfo找到对应的HandlerMethod目标方法

HandlerMapping

HandlerMapping:处理器映射

public interface HandlerMapping {
   // 返回此请求的 HandlerExecutionChain 对象(Handler、Interceptor)
   @Nullable
   HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

1、RequestMappingHandlerMapping就是用来处理标注了@xxxMapping注解的。基于注解来映射请求到处理器方法(最常用)

2、BeanNameUrlHandlerMapping:根据请求的URL路径与控制器Bean的名称进行匹配。如果URL路径与某个控制器的Bean名称相匹配,则该控制器将被选中来处理请求。

3、SimpleUrlHandlerMapping:通过直接配置URL路径与处理器的映射关系来处理请求。开发者可以在配置文件中指定哪些URL应该由哪些控制器处理,提供了比BeanNameUrlHandlerMapping更灵活的映射方式

4、WelcomePageHandlerMapping:用来处理欢迎页。比如访问 /就会跳转到index.html

5、RouterFunctionMapping:函数式编程风格的接口


@xxxMapping注解

5个HandlerMapping

RESTful风格原理

RESTful风格原理

如果定义了多个路径相同的方法但请求方式不同,它是怎么找到对应方法的?

那也就是说getMappingsByUrl()会根据路径找到多个RequestMappingInfo对象。比如这样:

最终会在getMatchingMapping()找到匹配的方法,核心就是根据不同的请求方式找不同的方法

利用RequestMappingInfo对象里的RequestMethodsRequestCondition对象做区分的

源码:org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition

相关文章
|
22天前
|
前端开发 Java Spring
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
这篇文章通过示例代码展示了如何在Spring MVC中编写和注册拦截器,以及如何在拦截器的不同阶段添加业务逻辑。
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
序-Servlet和SpringMVC的联系和区别-配置路径先想好使用的使用的方法,然后匹配的需要的技术
|
4月前
|
设计模式 前端开发 Java
[Spring ~源码] Spring的run方法以及SpringMVC执行流程
[Spring ~源码] Spring的run方法以及SpringMVC执行流程
|
4月前
|
Java Spring
SpringMVC控制层private方法中出现注入的service对象空指针异常
一、现象 SpringMVC中controller里的private接口中注入的service层的bean为null,而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。 controller中的方法被AOP进行了代理,普通Controller如果没有AOP,private方法中bean也是正常的。
|
11月前
|
JSON Java 数据格式
51SpringMVC - Controller方法返回值
51SpringMVC - Controller方法返回值
39 0
|
JSON 前端开发 Java
SpringMVC 方法三种类型返回值总结,你用过几种?
SpringMVC 方法三种类型返回值总结,你用过几种?
|
设计模式 JSON 前端开发
2021-08-11Spring MVC,入门项目搭建及流程,springMVC的适配器和映射器,基于注解的controller,映射请求,方法返回值,requestmapping注解
2021-08-11Spring MVC,入门项目搭建及流程,springMVC的适配器和映射器,基于注解的controller,映射请求,方法返回值,requestmapping注解
52 0
SpringMVC学习(五):向request域对象共享数据的五种方法
SpringMVC学习(五):向request域对象共享数据的五种方法
124 0
SpringMVC学习(五):向request域对象共享数据的五种方法
|
IDE Java 应用服务中间件
SpringMVC学习(一):IDEA2019创建Maven工程(默认纯手打方法)
SpringMVC学习(一):IDEA2019创建Maven工程(默认纯手打方法)
176 0
SpringMVC学习(一):IDEA2019创建Maven工程(默认纯手打方法)
|
前端开发 Java 索引
Spring MVC Controller 方法参数 Map 的实现类是什么?
问题 题主问题描述如下: 在SpringBoot中,Controller的参数中有Map接口类型的,请问他的实现类是什么? 突发奇想,在SpringBoot中,Controller的参数中有Map接口类型的
388 0
Spring MVC Controller 方法参数 Map 的实现类是什么?

相关实验场景

更多
下一篇
DDNS