Spring MVC 执行流程:HandlerMapping

Spring MVC 执行流程:https://www.cnblogs.com/jhxxb/p/10437384.html

 

@RequestMapping

@Target({ElementType.TYPE, ElementType.METHOD}) // 能够用到类上和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping // @Mapping 这个注解是 @since 3.0  但它目前还只有这个地方使用到了
public @interface RequestMapping {
    /**
     * 给这个 Mapping 取一个名字。若不填写,就用 HandlerMethodMappingNamingStrategy 去按规则生成
     */
    String name() default "";

    /**
     * 路径,数组形式,可以写多个。一般都是按照 Ant 风格进行书写
     */
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};

    /**
     * 请求方法:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
     * 可以指定多个方法。如果不指定,表示适配所有方法类型
     * 同时还有类似的枚举类:org.springframework.http.HttpMethod
     */
    RequestMethod[] method() default {};

    /**
     * 指定 request 中必须包含某些参数值时,才让该方法处理
     * 使用 params 元素,可以让多个处理方法处理到同一个 URL 的请求, 而这些请求的参数是不一样的
     * 如:@RequestMapping(value = "/fetch", params = {"personId=10"} 和 @RequestMapping(value = "/fetch", params = {"personId=20"}
     * 这两个方法都处理请求`/fetch`,但是参数不一样,进入的方法也不一样
     * 支持 !myParam 和 myParam!=myValue 这种
     */
    String[] params() default {};

    /**
     * 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求
     * @RequestMapping(value = "/head", headers = {"content-type=text/plain"}
     * @see org.springframework.http.MediaType
     */
    String[] headers() default {};

    /**
     * 指定处理请求 request 的提交内容类型(Content-Type),例如 application/json、text/html 等
     * 相当于只有指定的这些 Content-Type 的才处理
     * @RequestMapping(value = "/cons", consumes = {"application/json", "application/XML"}
     * 不指定表示处理所有,取值参见枚举类:org.springframework.http.MediaType
     * 可以使用 !text/plain 这样非的表达方式
     */
    String[] consumes() default {};

    /**
     * 指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型
     * 仅当 request 请求头中的(Accept)类型中包含该指定类型才返回
     * 可以使用 !text/plain 这样非的表达方式
     * @see org.springframework.http.MediaType
     */
    String[] produces() default {};
}

consumes、produces、params、headers 四个属性都是用来缩小请求范围。consumes 只能指定 content-Type 的内容类型,但是 headers 可以指定所有。所以可以认为 headers 包含 consumes 和 produces 的功能

Spring 提供了一个类:org.springframework.http.HttpHeaders,它里面有常量:包含几乎所有的请求头的 key

组合注解

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

 

源码

在 SpringMVC 中会有很多请求,每个请求都需要一个 Handler 处理,具体接收到一个请求之后使用哪个 Handler 进行处理呢?这就是 HandlerMapping 需要做的事。

HandlerMapping:负责映射用户的 URL 和对应的处理类 Handler,HandlerMapping 并没有规定这个 URL 与应用的处理类如何映射。所以在 HandlerMapping 接口中仅仅定义了根据一个 URL 必须返回一个由 HandlerExecutionChain 代表的处理链,我们可以在这个处理链中添加任意的 HandlerAdapter 实例来处理这个 URL 对应的请求(这样保证了最大的灵活性映射关系)。

public interface HandlerMapping {
    String BEST_MATCHING_HANDLER_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".bestMatchingHandler";
    String LOOKUP_PATH = org.springframework.web.servlet.HandlerMapping.class.getName() + ".lookupPath";
    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
    String BEST_MATCHING_PATTERN_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".bestMatchingPattern";
    String INTROSPECT_TYPE_LEVEL_MAPPING = org.springframework.web.servlet.HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".uriTemplateVariables";
    String MATRIX_VARIABLES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".matrixVariables";
    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = org.springframework.web.servlet.HandlerMapping.class.getName() + ".producibleMediaTypes";

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

HandlerMapping 有两大继承主线:MatchableHandlerMapping 和 AbstractHandlerMapping

 

WebApplicationObjectSupport 和 ApplicationObjectSupport 有点像是 ApplicationContextAware 和 ServletContextAware 的适配器,在允许继承的情况下,只需要继承此类就能自动拥有上面两个接口的功能,可以直接获取上下文等

@Service
public class HelloServiceImpl extends WebApplicationObjectSupport implements HelloService {
    @Override
    public Object hello() {
        // 继承自 ApplicationObjectSupport
        System.out.println(super.getApplicationContext());
        System.out.println(super.obtainApplicationContext()); // @since 5.0
        // MessageSourceAccessor 参考:MessageSourceAware,它是对 MessageSource 的一个包装,处理国际化
        System.out.println(super.getMessageSourceAccessor());

        // 继承自 WebApplicationObjectSupport
        System.out.println(super.getWebApplicationContext());
        System.out.println(super.getServletContext());
        System.out.println(super.getTempDir()); // Tomcat9_demowar\work\Catalina\localhost\demo_war_war

        return "service hello";
    }

    @Override
    protected void initApplicationContext() throws BeansException {
        // 父类为空实现,子类可以自行实现逻辑
        // 比如子类 AbstractDetectingUrlHandlerMapping 就复写了此方法去 detectHandlers();
        super.initApplicationContext();
    }
}

AbstractHandlerMapping 的子类都没有重写 getHandler() 这个方法

相当于 AbstractHandlerMapping 实现了对 getHandler() 方法的模版实现,它主要是对 HandlerInterceptor 进行了一个通用处理,最终会把它们放进 HandlerExecutionChain 里面去

/**
 * 自己额外实现了 BeanNameAware 和 Ordered 排序接口
 */
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware {
    @Nullable
    private Object defaultHandler;  // 默认的 Handler,使用的 Obejct,子类实现的时候,使用 HandlerMethod、HandlerExecutionChain 等
    private UrlPathHelper urlPathHelper = new UrlPathHelper(); // url 路径计算的辅助工具类
    private PathMatcher pathMatcher = new AntPathMatcher(); // Ant 风格的 Path 匹配模式,解决如 /books/{id} 场景
    private final List<Object> interceptors = new ArrayList<>(); // 保存着拦截器们
    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>(); // 从 interceptors 中解析得到,直接添加给全部 handler

    @Nullable
    private CorsConfigurationSource corsConfigurationSource; // 跨域相关的配置
    private CorsProcessor corsProcessor = new DefaultCorsProcessor();

    private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered 最低的顺序
    @Nullable
    private String beanName;

    public void setPathMatcher(PathMatcher pathMatcher) { // UrlPathHelper 我们可以自己指定
        Assert.notNull(pathMatcher, "PathMatcher must not be null");
        this.pathMatcher = pathMatcher;
        if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
            ((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setPathMatcher(pathMatcher);
        }
    }
    public PathMatcher getPathMatcher() { // PathMatcher 我们可以自己指定
        return this.pathMatcher;
    }

    public void setInterceptors(Object... interceptors) { // 可变参数:可以一次性添加多个拦截器,这里使用的 Object
        this.interceptors.addAll(Arrays.asList(interceptors));
    }

    public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { // 跨域设置
        Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
        if (!corsConfigurations.isEmpty()) {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.setCorsConfigurations(corsConfigurations);
            source.setPathMatcher(this.pathMatcher);
            source.setUrlPathHelper(this.urlPathHelper);
            source.setLookupPathAttributeName(LOOKUP_PATH);
            this.corsConfigurationSource = source;
        } else {
            this.corsConfigurationSource = null;
        }
    }
    public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) { // 重载方法
        Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
        this.corsConfigurationSource = corsConfigurationSource;
    }

    /**
     * 这步骤是最重要的。相当于父类 setApplicationContext 完成了之后,就会执行到这里
     * 这步可以看出,这里主要处理的都是拦截器相关的内容
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors); // 给子类扩展:增加拦截器,默认为空实现
        detectMappedInterceptors(this.adaptedInterceptors); // 找到所有 MappedInterceptor 类型的 bean 添加到 adaptedInterceptors 中
        // 将 interceptors 中的拦截器取出放入 adaptedInterceptors
        // 如果是 WebRequestInterceptor 类型的拦截器,需要用 WebRequestHandlerInterceptorAdapter 进行包装适配
        initInterceptors();
    }

    /**
     * 去容器(含祖孙容器)内找到所有的 MappedInterceptor 类型的拦截器,添加进去,非单例的 Bean 也包含
     * 备注:MappedInterceptor 为 SpringMVC 拦截器接口`HandlerInterceptor`的实现类,并且是个 final 类,Spring3.0 后出来的
     */
    protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false).values());
    }

    /**
     * 它就是把调用者放进来的 interceptors 们,适配成 HandlerInterceptor,然后统一放在`adaptedInterceptors`里面装着
     */
    protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }

    /**
     * 适配也很简单,就是支持源生的 HandlerInterceptor,以及 WebRequestInterceptor 两种情况
     */
    protected HandlerInterceptor adaptInterceptor(Object interceptor) {
        if (interceptor instanceof HandlerInterceptor) {
            return (HandlerInterceptor) interceptor;
        } else if (interceptor instanceof WebRequestInterceptor) {
            return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
        } else {
            throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
        }
    }

    @Nullable
    protected final HandlerInterceptor[] getAdaptedInterceptors() {
        return (!this.adaptedInterceptors.isEmpty() ? this.adaptedInterceptors.toArray(new HandlerInterceptor[0]) : null);
    }
    /**
     * 它只会返回 MappedInterceptor 这种类型的,上面是返回 adaptedInterceptors 所有
     */
    @Nullable
    protected final MappedInterceptor[] getMappedInterceptors() {
        List<MappedInterceptor> mappedInterceptors = new ArrayList<>(this.adaptedInterceptors.size());
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                mappedInterceptors.add((MappedInterceptor) interceptor);
            }
        }
        return (!mappedInterceptors.isEmpty() ? mappedInterceptors.toArray(new MappedInterceptor[0]) : null);
    }


    /**
     * 这个方法是该抽象类提供的一个非常重要的模版方法:根据 request 获取到一个 HandlerExecutionChain
     * 也是抽象类实现接口 HandlerMapping 的方法
     */
    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request); // 根据 request 获取对应的 handler,抽象方法,由具体的子类去实现
        if (handler == null) { // 若没有匹配上处理器,那就走默认的处理器,默认的处理器也是需要由子类给赋值,否则也会 null 的
            handler = getDefaultHandler();
        }
        if (handler == null) { // 若默认的处理器都没有,那就直接返回 null
            return null;
        }
        if (handler instanceof String) { // 如果是个 String 类型的名称,那就去容器内找这个 Bean,当作一个 Handler
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }
        // 关键步骤:根据 handler 和 request 构造一个请求处理链
        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)) { // Spring4.2 版本提供了对 CORS 跨域资源共享的支持
            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;
    }

    /**
     * 已经找到 handler,那就根据此 handler 构造一个请求链
     * 这里主要是把拦截器们给添加进来,构成对指定请求的一个拦截器链
     */
    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        // 小细节:因为 handler 本身也许就是个 Chain,所以此处需要判断一下
        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        // 此处就用到了 urlPathHelper 来解析 request
        // 请求地址为:`http://localhost:8080/demo_war_war/api/v1/hello`,那么 lookupPath = /api/v1/hello
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                // 这里其实就能体现出 MappedInterceptor 的些许优势了:也就是它只有路径匹配上了才会拦截,没有匹配上的就不会拦截了
                // 备注:MappedInterceptor 可以设置 includePatterns 和 excludePatterns 等
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else {
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }

MappedInterceptor

/**
 * @since 3.0
 * 是个 final 类,不允许直接使用继承的方式来扩展
 */
public final class MappedInterceptor implements HandlerInterceptor {
    @Nullable
    private final String[] includePatterns; // 可以为 null
    @Nullable
    private final String[] excludePatterns;

    private final HandlerInterceptor interceptor; // 持有一个 interceptor 的引用,类似于目标类

    // 注意:该类允许你自己指定路径的匹配规则。但是 Spring 里,不管哪个上层服务,默认使用的都是 Ant 风格的匹配
    // 并不是正则的匹配,所以效率上还是很高的
    @Nullable
    private PathMatcher pathMatcher;

    /**
     * 构造函数:不仅兼容 HandlerInterceptor,还可以转换 WebRequestInterceptor
     */
    public MappedInterceptor(@Nullable String[] includePatterns, WebRequestInterceptor interceptor) {
        this(includePatterns, null, interceptor);
    }

    public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns, WebRequestInterceptor interceptor) {
        // 此处使用 WebRequestHandlerInterceptorAdapter 这个适配器
        this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor));
    }

    public boolean matches(String lookupPath, PathMatcher pathMatcher) {
        // 原则:excludePatterns 先执行,includePatterns 后执行
        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;
                }
            }
        }
        // 如果 excludePatterns 执行完都没有匹配的,并且 includePatterns 是空的,那就返回 true(这是个处理方式技巧,对这种互斥的情况,这一步判断很关键)
        if (ObjectUtils.isEmpty(this.includePatterns)) {
            return true;
        }
        for (String pattern : this.includePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return true;
            }
        }
        return false;
    }

使用

@Configuration
@EnableWebMvc
@ComponentScan(value = "com.mvc", useDefaultFilters = false,
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
)
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    /**
     * 方式一:最源生的使用方式:直接注册进去即可
     * 支持 includePatterns 和 excludePatterns
     * 底层原理是依赖于一个`InterceptorRegistration`类,它是个普通类,协助创建一个`MappedInterceptor`
     * 由此可见最终底层还是使用的`MappedInterceptor`
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HelloInterceptor())
                .addPathPatterns() // 就是includePatterns
                .excludePathPatterns();
    }


    /**
     * 方式二:如果说上述方式是交给 Spring 去帮我们自动处理,这种方式相当于自己手动来处理
     * 请务必注意:此处的返回值必须是 MappedInterceptor,而不能是 HandlerInterceptor,否则不生效
     * 因为 BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false)
     * 这个方法它只去找 MappedInterceptor 类型,如果你是父类型,那就匹配不上了,这和工厂方法的 Bean 定义信息有关
     */
    @Bean
    public MappedInterceptor myHandlerInterceptor() {
        String[] includePatterns = {"/api/v1/hello"};
        MappedInterceptor handlerInterceptor = new MappedInterceptor(includePatterns, new HelloInterceptor());
        return handlerInterceptor;
    }
}

MatchableHandlerMapping

/**
 * @since 4.3.1
 */
public interface MatchableHandlerMapping extends HandlerMapping {
    @Nullable
    RequestMatchResult match(HttpServletRequest request, String pattern); // 确定给定的请求是否符合请求条件,pattern:模版
}

目前有两个类实现了此方法 RequestMappingHandlerMapping 和 AbstractUrlHandlerMapping,但是 Spring 内部还没有调用过此接口方法,此处跳过这部分

看 AbstractHandlerMapping 的真正实现类,主要分为两大主线:AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping

 

AbstractUrlHandlerMapping

属于 Spring 最早期的控制器实现。完全是基于类级别的:一个类就是一个 Handler,模式和源生的 Servlet 没太大差异。SpringMVC 默认给容器注入了该方式的实现类,由此可见 Spring 还是保持了充分的向下兼容的

和 URL 有关。它的大致思路为:将 url 对应的 Handler 保存在一个 Map 中,在 getHandlerInternal 方法中使用 url 从 Map 中获取 Handler

该抽象类提供了一个 Map,缓存着 URL 和它对应的 Handler,这是个非常重要的缓存。它提供了 registerHandler() 允许子类调用,向缓存里注册 url 和 handler 的对应关系

注意:此处不能把 Map 放出去让子类直接 put,因为程序必须要高内聚,才能保证更好的隔离性以及稳定性

public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
    @Nullable
    private Object rootHandler; // 根路径 / 的处理器
    private boolean useTrailingSlashMatch = false; // 是否使用斜线 / 匹配,如果为 true,那么`/users`它也会匹配上`/users/`,默认是 false 的
    private boolean lazyInitHandlers = false; // 是否延迟初始化 handler。仅适用于单实例 handler,默认是 false,表示立即实例化
    private final Map<String, Object> handlerMap = new LinkedHashMap<>(); // 这个 Map 就是缓存下 URL 对应的 Handler(注意这里只是 handler,而不是 chain)

    /**
     * 这个就是父类留给子类实现的抽象方法,此抽象类相当于进行了进一步的模版实现
     */
    @Override
    @Nullable
    protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        // 找到 URL 的后半段:如`/api/v1/hello`,由此可见 SpringMVC 处理 URL 路径匹配都是从工程名后面开始匹配的
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        // 根据 url 查找 handler
        // 1、先去 handlerMap 里找,若找到了那就实例化它,并且并且给 chain 里加入一个拦截器:`PathExposingHandlerInterceptor`,它是个 private 私有类的 HandlerInterceptor
        // 该拦截器的作用:request.setAttribute() 请求域里面放置四个属性,key 见 HandlerMapping 的常量
        // 2、否则就使用 PathMatcher 去匹配 URL,这里面光匹配其实是比较简单的。但是这里面还解决了一个问题:那就是匹配上多个路径的问题
        // 因此:若匹配上多个路径了,就按照 PathMatcher 的排序规则排序,取值 get(0),最后就是同上,加上那个 HandlerInterceptor 即可
        // 需要注意的是:若存在 uriTemplateVariables,也就是路径里都存在多个最佳的匹配的情况,比如 /book/{id} 和 /book/{name} 这两种
        // 还有就是 URI 完全一样,但是一个是 get 方法,一个是 post 方法之类的,那就再加一个拦截器`UriTemplateVariablesHandlerInterceptor`,它 request.setAttribute() 了一个属性:key为 xxx.uriTemplateVariables
        // 这些 Attribute 后续都是有用的,请注意:这里默认的两个拦截器每次都是 new 出来的,和 Handler 可以说是绑定的,所以不会存在线程安全问题
        request.setAttribute(LOOKUP_PATH, lookupPath);
        Object handler = lookupHandler(lookupPath, request);
        if (handler == null) { // 若没找到
            // 处理跟路径 / 和默认的 Handler
            Object rawHandler = null;
            if (StringUtils.matchesCharacter(lookupPath, '/')) {
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = obtainApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                // 就是注册上面说的默认的两个拦截器,第四个参数为 null,就只会注册一个拦截器
                // 然后把 rawHandler 转换成 chain(这个时候 chain 里面可能已经有两个拦截器了,然后父类还会继续把用户自定义的拦截器放上去)
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        return handler;
    }

    /**
     * 该抽象类提供的这个方法就特别重要了:向 handlerMap 里面 put 值的唯一入口,可以批量 urls
     */
    protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
        Assert.notNull(urlPaths, "URL path array must not be null");
        for (String urlPath : urlPaths) {
            registerHandler(urlPath, beanName);
        }
    }

    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        if (!this.lazyInitHandlers && handler instanceof String) { // 如果是 beanName,并且它是立马加载的
            String handlerName = (String) handler;
            ApplicationContext applicationContext = obtainApplicationContext();
            if (applicationContext.isSingleton(handlerName)) { // 并且还是单例的,那就立马实例化吧
                resolvedHandler = applicationContext.getBean(handlerName);
            }
        }

        Object mappedHandler = this.handlerMap.get(urlPath); // 先尝试从 Map 中去获取
        if (mappedHandler != null) {
            // 一个 URL 只能映射到一个 Handler 上(但是一个 Handler 是可以处理多个 URL 的,这个需要注意)
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException("Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        } else {
            if (urlPath.equals("/")) { // 如果你的 handler 处理的路径是根路径
                if (logger.isTraceEnabled()) {
                    logger.trace("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            } else if (urlPath.equals("/*")) { // 这个路径相当于处理所有优先级是最低的,所以当作默认的处理器来使用
                if (logger.isTraceEnabled()) {
                    logger.trace("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            } else { // 正常路径的
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isTraceEnabled()) { // trace 级别日志
                    logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }

    public final Map<String, Object> getHandlerMap() { // 该缓存也提供了一个只读视图给调用者访问
        return Collections.unmodifiableMap(this.handlerMap);
    }

AbstractDetectingUrlHandlerMapping

是个抽象类,继承自 AbstractUrlHandlerMapping。它就越来越具有功能化了:Detecting 表明它是有检测 URL 的功能的

AbstractDetectingUrlHandlerMapping 是通过扫描方式注册 Handler,收到请求时由 AbstractUrlHandlerMapping 的 getHandlerInternal 进行分发,看看到底是交给哪个 Handler 进行处理

/**
 * @since 2.5
 */
public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {
    // 是否要去祖先容器里面检测所有的 Handlers,默认是 false,表示只在自己的容器里面找
    // 若设置为 true,相当于在父容器里的 Controller 也会被挖出来,一般不建议这么去做
    private boolean detectHandlersInAncestorContexts = false;

    @Override
    public void initApplicationContext() throws ApplicationContextException {
        super.initApplicationContext();
        detectHandlers(); // 这里是检测的入口
    }

    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        // 默认只会在当前容器里面去查找检测,这里使用的 Object.class,说明是把本容器内所有类型的 Bean 定义都拿出来了
        String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            // 这是个抽象方法由子类去实现。它的作用就是看看 url 和 bean 怎么才算是匹配,也就是说这个 handler 到底能够处理哪些 URL
            // 注意:此处还是类级别(Bean),相当于一个类就是一个 Handler
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                registerHandler(urls, beanName); // 注册进去,缓存起来
            }
        }

        if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
            logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
        }
    }

BeanNameUrlHandlerMapping

它是 AbstractDetectingUrlHandlerMapping 的唯一实现类(Spring5 以上)

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
    @Override
    protected String[] determineUrlsForHandler(String beanName) {
        List<String> urls = new ArrayList<>();
        // 意思就是必须以 / 开头才行,这算是一种约定吧
        // 这种方式和 @WebServlet 方式一模一样
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        // 别名也可以
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }
}

SimpleUrlHandlerMapping

它是 AbstractUrlHandlerMapping 的直接实现类,也是一个基于Map的简单实现。就是把开发者指定的一个 Map,在容器启动的时候把它注册进去,当然我们自己已经知道了 URL 和 Handler 的映射关系了,然后需要进一步构造出一个 HandlerMapping 的时候,或许它是一个较快解决问题的选择,它最重要的是 urlMap 这个参数

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
    private final Map<String, Object> urlMap = new LinkedHashMap<>();public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
        setUrlMap(urlMap);
        setOrder(order);
    }

    public void setMappings(Properties mappings) {
        CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
    }

    public void setUrlMap(Map<String, ?> urlMap) {
        this.urlMap.putAll(urlMap);
    }

    @Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        registerHandlers(this.urlMap);
    }

    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        if (urlMap.isEmpty()) {
            logger.trace("No patterns in " + formatMappingName());
        } else {
            urlMap.forEach((url, handler) -> {
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            });
            if (logger.isDebugEnabled()) {
                List<String> patterns = new ArrayList<>();
                if (getRootHandler() != null) { // 如果还没有 /,在前面加上 /
                    patterns.add("/");
                }
                if (getDefaultHandler() != null) {
                    patterns.add("/**");
                }
                patterns.addAll(getHandlerMap().keySet());
                logger.debug("Patterns " + patterns + " in " + formatMappingName());
            }
        }
    }

SimpleUrlHandlerMapping 使用

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property>
        <map>
            <entry key="/hello.do" value="myController"/>
            <entry key="/my.do" value="myController"/>
        </map>
    </property>
</bean>

对应的 Controller

// 我们的 Controller 类必须(直接|间接)实现 org.springframework.web.servlet.mvc.Controller 接口,才被认为是一个控制器,否则无法判断具体处理方法是谁
@Controller("/hello") // 此处 BeanName 必须是 / 开头,否则是不会作为 handler 的
public class HelloController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("this is my demo");
        return null;
    }
}

 

AbstractHandlerMethodMapping

将 method 作为 handler 来使用的,比如 @RequestMapping 所注释的方法就是这种 handler(当然它并不强制你一定得使用 @RequestMapping 这样的注解)

在前面我们已经知道了 AbstractHandlerMethodMapping 的父类 AbstractHandlerMapping,其定义了抽象方法 getHandlerInternal(HttpServletRequest request),那么这里主要看看它对此抽象方法的实现

/**
 * @since 3.1
 * 实现了 initializingBean 接口,其实主要的注册操作则是通过 afterPropertiesSet() 接口方法来调用的
 * 它是带有泛型 T 的,T:包含 HandlerMethod 与传入请求匹配所需条件的 handlerMethod 的映射
 */
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; // SCOPED_TARGET 的 BeanName 的前缀
    private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
    private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration(); // 跨域相关
    static {
        ALLOW_CORS_CONFIG.addAllowedOrigin("*");
        ALLOW_CORS_CONFIG.addAllowedMethod("*");
        ALLOW_CORS_CONFIG.addAllowedHeader("*");
        ALLOW_CORS_CONFIG.setAllowCredentials(true);
    }
    private boolean detectHandlerMethodsInAncestorContexts = false; // 默认不会去祖先容器里面找 Handlers
    // @since 4.1 提供的新接口
    // 为 HandlerMetho 的映射分配名称的策略接口,只有一个方法 getName()
    // 唯一实现为:RequestMappingInfoHandlerMethodMappingNamingStrategy
    // 策略为:@RequestMapping 指定了 name 属性,那就以指定的为准,否则策略为:取出 Controller 所有的`大写字母` + # + method.getName()
    // 如:AppoloController#match方法,最终的 name 为:AC#match
    // 当然这个你也可以自己实现这个接口,然后 set 进来即可(只是一般没啥必要这么去干)
    @Nullable
    private HandlerMethodMappingNamingStrategy<T> namingStrategy;
    private final MappingRegistry mappingRegistry = new MappingRegistry(); // 内部类,负责注册

    /**
     * 此处细节:使用的是读写锁,比如此处使用的是读锁,获得所有注册进去的 Handler 的 Map
     */
    public Map<T, HandlerMethod> getHandlerMethods() {
        this.mappingRegistry.acquireReadLock();
        try {
            return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
        } finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

    /**
     * 此处是根据 mappingName 来获取一个 Handler
     */
    @Nullable
    public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) {
        return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName);
    }

    /**
     * 最终都是委托给 mappingRegistry 去做了注册的工作,此处日志级别为 trace
     */
    public void registerMapping(T mapping, Object handler, Method method) {
        if (logger.isTraceEnabled()) {
            logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
        }
        this.mappingRegistry.register(mapping, handler, method);
    }
    public void unregisterMapping(T mapping) {
        if (logger.isTraceEnabled()) {
            logger.trace("Unregister mapping \"" + mapping + "\"");
        }
        this.mappingRegistry.unregister(mapping);
    }

    @Override
    public void afterPropertiesSet() {
        initHandlerMethods(); // 这个很重要,是初始化 HandlerMethods 的入口
    }

    /**
     * 看 initHandlerMethods(),观察是如何实现加载 HandlerMethod
     */
    protected void initHandlerMethods() {
        // getCandidateBeanNames:Object.class 相当于拿到当前容器(一般都是当前容器)内所有的 Bean 定义信息
        // 如果容器隔离好的话,这里一般只会拿到 @Controller 标注的 web 组件,以及其它相关 web 组件的,不会非常多的
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // BeanName 不是以这个打头的,这里才会 process 这个 BeanName
                processCandidateBean(beanName); // 会在每个Bean里面找处理方法,HandlerMethod,然后注册进去
            }
        }
        handlerMethodsInitialized(getHandlerMethods()); // 就是输出一句日志
    }

    /**
     * 确定指定的候选 bean 的类型,如果标识为 Handler 类型,则调用 detectHandlerMethods 方法
     * isHandler(beanType):判断这个 type 是否为 Handler 类型,它是个抽象方法,由子类去决定到底啥才叫 Handler
     * `RequestMappingHandlerMapping`的判断依据为:该类上标注了 @Controller 注解或者 @Component 注解,就算作是一个 Handler
     * 所以此处:@Controller 起到了一个特殊的作用,不能等价于 @Component 的
     */
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        } catch (Throwable ex) { // 即使抛出异常,程序也不会终止
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

    /**
     * 在指定的 Handler 的 bean 中查找处理程序方法 Methods,找到就注册进去:mappingRegistry
     */
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // https://blog.csdn.net/f641385712/article/details/89380067
            // 主要用到了 MethodIntrospector.selectMethods 这个内省方法工具类的这个工具方法,去找指定 Class 里面,符合条件的方法们
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            // 过滤 Method 的核心逻辑
                            // getMappingForMethod 属于一个抽象方法,由子类去决定它的寻找规则:什么才算作一个处理器方法
                            return getMappingForMethod(method, userType);
                        } catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
                        }
                    });
            if (logger.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            // 把找到的 Method 一个个遍历,注册进去
            methods.forEach((method, mapping) -> {
                // 找到这个可调用的方法(AopUtils.selectInvocableMethod)
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

该抽象类完成了所有的 Handler 以及 handler 里面所有的 HandlerMethod 的模版操作,但是决定哪些 Bean 是 Handler 类,哪些方法才是 HandlerMathod,这些逻辑都是交给子类自己去实现,并没有把 Handler 的实现方式定死,允许不同

这里面有个核心内容:那就是注册 handlerMethod,是交给 AbstractHandlerMethodMapping 的一个内部类 MappingRegistry 去完成的,用来专门维持所有的映射关系,并提供查找方法

AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心

维护几个 Map(键值对),用来存储映射的信息, 还有一个 MappingRegistration 专门保存注册信息

MappingRegistration:就是一个 private 的内部类,维护着 T mapping、HandlerMethod handlerMethod、List<String> directUrls、String mappingName 等信息,提供 get 方法访问。没有任何其它逻辑

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    class MappingRegistry {
        private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); // mapping 对应的 MappingRegistration 对象
        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); // 保存着 mapping 和 HandlerMethod 的对应关系
        // 保存着 URL 与匹配条件(mapping)的对应关系,当然这里的 URL 是 pattern 式的,可以使用通配符
        // 这里的 Map 不是普通的 Map,而是 MultiValueMap,它是个多值 Map。其实它的 value 是一个 list 类型的值
        // 至于为何是多值?有这么一种情况,URL 都是 /api/v1/hello,但是有的是 get post delete 等方法,所以有可能是会匹配到多个 MappingInfo 的
        private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
        // 这个 Map 是 SpringMVC4.1 新增的(毕竟这个策略接口 HandlerMethodMappingNamingStrategy 在 Spring4.1 后才有,这里的 name 是它生成出来的)
        // 保存着 name 和 HandlerMethod 的对应关系(一个 name 可以有多个 HandlerMethod)
        private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
        private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); // 跨域
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁,读写分离,提高启动效率

        // 读锁提供给外部访问,写锁自己放在内部即可
        public void acquireReadLock() {
            this.readWriteLock.readLock().lock();
        }
        public void releaseReadLock() {
            this.readWriteLock.readLock().unlock();
        }

        /**
         * 注册 Mapping 和 handler,以及 Method,此处写锁保证线程安全
         */
        public void register(T mapping, Object handler, Method method) {
            // Assert that the handler method is not a suspending one.
            if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
                    throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
                }
            }
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 此处注意:都是 new HandlerMethod() 了一个新的出来
                // 同样的:一个 URL Mapping 只能对应一个 Handler,这里可能会出现常见的一个异常信息:Ambiguous mapping. Cannot map XXX
                validateMethodMapping(handlerMethod, mapping);
                this.mappingLookup.put(mapping, handlerMethod); // 缓存 Mapping 和 handlerMethod 的关系

                // 保存 url 和 RequestMappingInfo(mapping)对应关系
                // 这里注意:多个 url 可能对应着同一个 mappingInfo,毕竟 @RequestMapping 的 url 是可以写多个的
                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }
                // 保存 name 和 handlerMethod 的关系,同样也是一对多
                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }
                // 注册 mapping 和 MappingRegistration 的关系
                this.registry.put(mapping, MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            } finally {
                this.readWriteLock.writeLock().unlock(); // 释放锁
            }
        }

        public void unregister(T mapping) { // 相当于进行一次逆向操作

        private void removeMappingName(MappingRegistration<T> definition) {

这个注册中心,核心是保存了多个 Map 映射关系,相当于缓存下来。在请求过来时需要查找的时候,可以迅速定位到处理器

再看 AbstractHandlerMethodMapping 对父类抽象方法:getHandlerInternal 的实现

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 要进行匹配的请求的 URI path
        request.setAttribute(LOOKUP_PATH, lookupPath);
        this.mappingRegistry.acquireReadLock();
        try {
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //委托给方法 lookupHandlerMethod() 去找到一个 HandlerMethod 去最终处理
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        } finally {
            this.mappingRegistry.releaseReadLock();
        }
    }

    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>(); // Match 是一个 private class,内部就两个属性:T mapping 和 HandlerMethod handlerMethod
        // 根据 lookupPath 去注册中心里查找 mappingInfos,因为一个具体的 url 可能匹配上多个 MappingInfo 的
        // 至于为何是多值?有这么一种情况,URL 都是 /api/v1/hello,但是有的是 get post delete 等方法,当然还有可能是 headers/consumes 等等不一样,都算多个的,所以有可能是会匹配到多个 MappingInfo 的
        // 所有这个里可以匹配出多个出来。比如 /hello 匹配出 GET、POST、PUT 都成,所以 size 可以为 3
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            // 依赖于子类实现的抽象方法:getMatchingMapping(),看看到底匹不匹配,而不仅仅是 URL 匹配就行
            // 比如还有 method、headers、consumes 等等这些不同,都代表着不同的 MappingInfo 的
            // 最终匹配上的,会 new Match() 放进 matches 里面去
            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()) { // 但凡只要找到了一个匹配,就进来这里了,请注意:因为到这里,匹配上的可能还不止一个,所以才需要继续处理
            // 排序后的最佳匹配为get(0)
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) { // 如果总的匹配个数大于 1 的话
                // getMappingComparator 这个方法也是抽象方法由子类去实现的。
                // 比如:`RequestMappingInfoHandlerMapping` 的实现为先比较 Method,patterns、params
                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); // 次最佳匹配
                // 如果发现次最佳匹配和最佳匹配比较是相等的,那就报错
                // Ambiguous handler methods mapped for
                // 注意:这个是运行时的检查,在启动的时候是检查不出来的,所以运行期的这个检查也是很有必要的,否则就会出现意想不到的效果
                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 的属性里面
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            // 它也是做了一件事:request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath)
            handleMatch(bestMatch.mapping, lookupPath, request);
            // 最终返回的是 HandlerMethod
            return bestMatch.handlerMethod;
        } else { // 一个都没匹配上,handleNoMatch 这个方法虽然不是抽象方法,但子类 RequestMappingInfoHandlerMapping 有复写此方法
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        // 因为上面说了 mappings 可能会有多个,比如 get post put 的都算,这里就是要进行筛选出所有 match 上的
        for (T mapping : mappings) {
            // 只有 RequestMappingInfoHandlerMapping 实现了一句话:return info.getMatchingCondition(request);
            // 因此 RequestMappingInfo#getMatchingCondition() 方法里大有文章可为
            // 它会对所有的 methods、params、headers... 都进行匹配,但凡匹配不上的就返回 null
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }

RequestMappingInfoHandlerMapping

提供匹配条件 RequestMappingInfo 的解析处理,它主要做的事就是确定了泛型类型为:RequestMappingInfo,然后很多方法都依托它来完成判定逻辑

而 RequestMappingInfo 的构建工作,SpringMVC 理论上是可以允许有多种方案。SpringMVC 给出的唯一实现类为 RequestMappingHandlerMapping,SpringMVC 目前的唯一构造方案:通过 @RequestMapping 来构造一个 RequestMappingInfo

/**
 * @since 3.1 此处泛型为:RequestMappingInfo 用这个类来表示 mapping 映射关系、参数、条件等
 */
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
    private static final Method HTTP_OPTIONS_HANDLE_METHOD; // 专门处理 Http 的 Options 方法的 HandlerMethod
    static {
        try {
            HTTP_OPTIONS_HANDLE_METHOD = HttpOptionsHandler.class.getMethod("handle");
        } catch (NoSuchMethodException ex) {
            // Should never happen
            throw new IllegalStateException("Failed to retrieve internal handler method for HTTP OPTIONS", ex);
        }
    }

    protected RequestMappingInfoHandlerMapping() { // 构造函数:给 set 了一个 HandlerMethodMappingNamingStrategy
        setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
    }

    @Override
    protected Set<String> getMappingPathPatterns(RequestMappingInfo info) { // 复写父类的抽象方法:获取 mappings 里面的 patters 们
        return info.getPatternsCondition().getPatterns();
    }

    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
        return info.getMatchingCondition(request); // 校验看看这个 Mapping 是否能匹配上这个 request,若能匹配上就返回一个 RequestMappingInfo
    }

    @Override
    protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
        return (info1, info2) -> info1.compareTo(info2, request);
    }

RequestMappingInfo

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
    private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition();
    private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition();
    private static final ParamsRequestCondition EMPTY_PARAMS = new ParamsRequestCondition();
    private static final HeadersRequestCondition EMPTY_HEADERS = new HeadersRequestCondition();
    private static final ConsumesRequestCondition EMPTY_CONSUMES = new ConsumesRequestCondition();
    private static final ProducesRequestCondition EMPTY_PRODUCES = new ProducesRequestCondition();
    private static final RequestConditionHolder EMPTY_CUSTOM = new RequestConditionHolder(null);


    @Nullable
    private final String name;
    // 这些个匹配器都继承自 AbstractRequestCondition,会进行各自的匹配工作
    // 它们顶级抽象接口为:RequestCondition  @since 3.1
    private final PatternsRequestCondition patternsCondition;
    private final RequestMethodsRequestCondition methodsCondition;
    private final ParamsRequestCondition paramsCondition;
    private final HeadersRequestCondition headersCondition;
    private final ConsumesRequestCondition consumesCondition;
    private final ProducesRequestCondition producesCondition;
    private final RequestConditionHolder customConditionHolder;
    private final int hashCode;

    public RequestMappingInfo(@Nullable String name, @Nullable PatternsRequestCondition patterns,
                              @Nullable RequestMethodsRequestCondition methods, @Nullable ParamsRequestCondition params,
                              @Nullable HeadersRequestCondition headers, @Nullable ConsumesRequestCondition consumes,
                              @Nullable ProducesRequestCondition produces, @Nullable RequestCondition<?> custom) {
        this.name = (StringUtils.hasText(name) ? name : null);
        this.patternsCondition = (patterns != null ? patterns : EMPTY_PATTERNS);
        this.methodsCondition = (methods != null ? methods : EMPTY_REQUEST_METHODS);
        this.paramsCondition = (params != null ? params : EMPTY_PARAMS);
        this.headersCondition = (headers != null ? headers : EMPTY_HEADERS);
        this.consumesCondition = (consumes != null ? consumes : EMPTY_CONSUMES);
        this.producesCondition = (produces != null ? produces : EMPTY_PRODUCES);
        this.customConditionHolder = (custom != null ? new RequestConditionHolder(custom) : EMPTY_CUSTOM);
        this.hashCode = calculateHashCode(
                this.patternsCondition, this.methodsCondition, this.paramsCondition, this.headersCondition,
                this.consumesCondition, this.producesCondition, this.customConditionHolder);
    }

    /**
     * 因为类上和方法上都可能会有 @RequestMapping 注解,所以这里是把语意合并,该方法来自顶层接口
     */
    @Override
    public RequestMappingInfo combine(RequestMappingInfo other) {
        String name = combineNames(other);
        PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
        RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
        ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
        HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
        ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
        ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
        RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

        return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition());
    }

    /**
     * 合并后,就开始发挥作用了,该接口来自于顶层接口
     */
    @Override
    @Nullable
    public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
        if (methods == null) {
            return null;
        }
        ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
        if (params == null) {
            return null;
        }
        HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
        if (headers == null) {
            return null;
        }
        ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
        if (consumes == null) {
            return null;
        }
        ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
        if (produces == null) {
            return null;
        }
        PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
        if (patterns == null) {
            return null;
        }
        RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
        if (custom == null) {
            return null;
        }

        return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition());
    }

SpringMVC 请求 URL 带后缀匹配的情况,如 /hello.json 也能匹配 /hello

RequestMappingInfoHandlerMapping 在处理 http 请求的时候, 如果请求 url 有后缀,如果找不到精确匹配的那个 @RequestMapping 方法。那么,就把后缀去掉,然后 .* 去匹配,这样,一般都可以匹配。

比如有一个 @RequestMapping("/rest"),那么精确匹配的情况下,只会匹配 /rest 请求。如果前端发来一个 /rest.abcdef 这样的请求,又没有配置 @RequestMapping("/rest.abcdef") 这样映射的情况下,@RequestMapping("/rest") 就会生效。

究其原因就到了 PatternsRequestCondition 这个类上,具体实现是它的匹配逻辑来决定的。

public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
    private final static Set<String> EMPTY_PATH_PATTERN = Collections.singleton("");
    private final Set<String> patterns;
    private final UrlPathHelper pathHelper;
    private final PathMatcher pathMatcher;
    private final boolean useSuffixPatternMatch;
    private final boolean useTrailingSlashMatch;
    private final List<String> fileExtensions = new ArrayList<>();

    @Override
    @Nullable
    public PatternsRequestCondition getMatchingCondition(HttpServletRequest request) {
        // patterns表示此MappingInfo可以匹配的值们。一般对应@RequestMapping注解上的patters数组的值
        // 拿到待匹配的值,比如此处为"/hello.json"
        String lookupPath = this.pathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
        // 最主要就是这个方法了,它拿着这个lookupPath匹配
        List<String> matches = getMatchingPatterns(lookupPath);
        // 此处如果为empty,就返回null了
        return !matches.isEmpty() ? new PatternsRequestCondition(new LinkedHashSet<>(matches), this) : null;
    }

    public List<String> getMatchingPatterns(String lookupPath) {
        List<String> matches = null;
        for (String pattern : this.patterns) {
            // 最最最重点就是在 getMatchingPattern() 这个方法里,拿着 lookupPath 和 pattern 看它俩合拍不
            String match = getMatchingPattern(pattern, lookupPath);
            if (match != null) {
                matches = (matches != null ? matches : new ArrayList<>());
                matches.add(match);
            }
        }
        if (matches == null) {
            return Collections.emptyList();
        }
        if (matches.size() > 1) { // 解释一下为何匹配的可能是多个。因为 url 匹配上了,但是还有可能 @RequestMapping 的其余属性匹配不上,所以此处需要注意,是可能匹配上多个的,最终是唯一匹配就成
            matches.sort(this.pathMatcher.getPatternComparator(lookupPath));
        }
        return matches;
    }

    /**
     * url 的真正匹配规则,非常重要
     * 注意这个方法的取名,上面是负数,这里是单数
     */
    @Nullable
    private String getMatchingPattern(String pattern, String lookupPath) {
        if (pattern.equals(lookupPath)) { // 完全相等,那就不继续了
            return pattern;
        }
        // 注意了:useSuffixPatternMatch 这个属性就是我们最终要关闭后缀匹配的关键
        // 这个值默外部给传的 true(其实内部默认值是 boolean 类型为 false)
        if (this.useSuffixPatternMatch) {
            // 这个意思是若 useSuffixPatternMatch=true,就支持后缀匹配。还可以配置 fileExtensions 让只支持我们自定义的后缀匹配,而不是下面最终的 .* 全部支持
            if (!this.fileExtensions.isEmpty() && lookupPath.indexOf('.') != -1) {
                for (String extension : this.fileExtensions) {
                    if (this.pathMatcher.match(pattern + extension, lookupPath)) {
                        return pattern + extension;
                    }
                }
            } else { // 若没有配置指定后缀匹配,并且你的 handler 也没有 .* 这样匹配的,那就默认你的 pattern 就给你添加上后缀 ".*",表示匹配所有请求的 url 的后缀
                boolean hasSuffix = pattern.indexOf('.') != -1;
                if (!hasSuffix && this.pathMatcher.match(pattern + ".*", lookupPath)) {
                    return pattern + ".*";
                }
            }
        }
        if (this.pathMatcher.match(pattern, lookupPath)) { // 若匹配上了,直接返回此 patter
            return pattern;
        }
        // 这又是它支持的匹配规则。默认 useTrailingSlashMatch 它也是 true
        // 这就是为何我们的 /hello/ 也能匹配上 /hello 的原因
        if (this.useTrailingSlashMatch) {
            if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
                return pattern + "/";
            }
        }
        return null;
    }

SpringMVC 中配置此功能

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    // 关闭后缀名匹配,关闭最后一个 / 匹配
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseSuffixPatternMatch(false);
        configurer.setUseTrailingSlashMatch(false);
    }
}

RequestMappingHandlerMapping

唯一实现类,根据 @RequestMapping 注解生成 RequestMappingInfo,同时提供 isHandler 实现。直到这个具体实现类,才与具体的实现方式 @RequestMapping 做了强绑定,有了三层抽象的实现,留给本类需要实现的功能已经不是非常的多了

/**
 * @since 3.1  Spring3.1 才提供的这种注解扫描的方式的支持,它也实现了 MatchableHandlerMapping 分支的接口
 * EmbeddedValueResolverAware 接口:说明要支持解析 Spring 的表达式
 */
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {
    private boolean useSuffixPatternMatch = true;
    private boolean useRegisteredSuffixPatternMatch = false;
    private boolean useTrailingSlashMatch = true;
    private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
    @Nullable
    private StringValueResolver embeddedValueResolver;
    private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

    /**
     * 配置要应用于控制器方法的路径前缀
     *
     * @since 5.1:Spring5.1 才出来的新特性
     * 前缀用于丰富每个 @RequestMapping 方法的映射,至于匹不匹配由 Predicate 来决定,有种前缀分类的效果
     * 推荐使用 Spring5.1 提供的类:org.springframework.web.method.HandlerTypePredicate
     */
    public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) {
        this.pathPrefixes = Collections.unmodifiableMap(new LinkedHashMap<>(prefixes));
    }

    /**
     * @since 5.1
     * 注意 pathPrefixes 是只读的,因为上面 Collections.unmodifiableMap 了,有可能只是个空 Map
     */
    public Map<String, Predicate<Class<?>>> getPathPrefixes() {
        return this.pathPrefixes;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration(); // 对 RequestMappingInfo 的配置进行初始化赋值
        this.config.setUrlPathHelper(getUrlPathHelper()); // 设置 urlPathHelper 默认为 UrlPathHelper.class
        this.config.setPathMatcher(getPathMatcher()); // 默认为 AntPathMatcher,路径匹配校验器
        this.config.setSuffixPatternMatch(useSuffixPatternMatch()); // 是否支持后缀补充,默认为 true
        this.config.setTrailingSlashMatch(useTrailingSlashMatch()); // 是否添加 / 后缀,默认为 true
        this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch()); // 是否采用 mediaType 匹配模式,比如 .json/.xml 模式的匹配,默认为 false
        this.config.setContentNegotiationManager(getContentNegotiationManager()); // mediaType处理类:ContentNegotiationManager

        super.afterPropertiesSet(); // 此处,必须要调用父类的方法
    }

    /**
     * 判断该类,是否是一个 handler(此处就体现出 @Controller 注解的特殊性了)
     * 这也是为何我们的 XXXController 用 @Bean 申明是无效的原因(前提是类上没有 @RequestMapping 注解,否则也是可以的)
     * 因此个人建议:为了普适性,类上的 @RequestMapping 也统一要求加上,即使你不写 @Value 也没关系,这样是最好的
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

    /**
     * 父类:AbstractHandlerMethodMapping#detectHandlerMethods 的时候,会去该类里面找所有的指定的方法
     * 而什么叫指定的?就是靠这个来判定方法来确定是否符合条件
     */
    @Override
    @Nullable
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo info = createRequestMappingInfo(method); // 第一步:先拿到方法上的 info
        if (info != null) { // 方法上有。再第二步:拿到类上的 info
            RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
            if (typeInfo != null) {
                // 倘若类上面也有,那就 combine 把两者结合
                // combile 的逻辑基如下:
                // names:name1+#+name2
                // path:路径拼接起来作为全路径(容错了方法里没有 / 的情况)
                // method、params、headers:取并集
                // consumes、produces:以方法的为准,没有指定再取类上的
                // custom:谁有取谁的。若都有:那就看 custom 具体实现的 .combine 方法去决定,简单的说就是交给调用者了
                info = typeInfo.combine(info);
            }
            // 在 Spring5.1 之后还要处理这个前缀匹配
            // 根据这个类,去找看有没有前缀  getPathPrefix():entry.getValue().test(handlerType) = true 算是匹配上了
            // 备注:也支持 ${os.name} 这样的语法拿值,可以把前缀也写在专门的配置文件里
            String prefix = getPathPrefix(handlerType);
            if (prefix != null) {
                // RequestMappingInfo.paths(prefix) 相当于统一在前面加上这个前缀
                info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
            }
        }
        return info;
    }

    @Nullable
    private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
        // 此处使用的是 findMergedAnnotation,这也就是为什么虽然 @RequestMapping 它并不具有继承的特性,但是你子类仍然有继承的效果的原因
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
        // 这里进行了区分处理,如果是 Class 的话、如果是 Method 的话
        // 这里返回的是一个 condition,也就是看看要不要处理这个请求的条件
        RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
        // 这个 createRequestMappingInfo 就是根据一个 @RequestMapping 以及一个 condition 创建一个
        // 显然如果没有找到此注解,这里就返回 null 了,表明这个方法啥的就不是一个 info
        return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
    }

    /**
     * 它俩都是返回的 null。protected 方法留给子类复写,子类可以据此自己定义一套自己的规则来限制匹配
     * Provide a custom method-level request condition.
     * 它相当于在 Spring MVC 默认规则的基础上,用户还可以自定义条件进行处理
     */
    @Nullable
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        return null;
    }
    @Nullable
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        return null;
    }

    /**
     * 根据 @RequestMapping 创建一个 RequestMappingInfo
     */
    protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

        RequestMappingInfo.Builder builder = RequestMappingInfo
                // 强大的地方在此处:path 支持 /api/v1/${os.name}/hello 这样形式动态的获取值
                // 也就是说 URL 还可以从配置文件里面读取
                // @GetMapping("/${os.name}/hello") // 支持从配置文件里读取此值
                .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name());
        if (customCondition != null) { // 调用者自定义的条件
            builder.customCondition(customCondition);
        }
        // 注意此处:把当前的 config 设置进去了
        return builder.options(this.config).build();
    }

    @Override
    protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { // 支持了 @CrossOrigin 注解,Spring4.2 提供的注解

全局统一配置 Controller 前缀,如 /api/v1

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        // configurer.setUseSuffixPatternMatch(false); // 关闭后缀名匹配
        // configurer.setUseTrailingSlashMatch(false); // 关闭最后一个 / 匹配

        // 这样 HelloController 上的方法自动就会有此前缀了,而别的 controller 上是不会有的
        // 注意:这是 Spring5.1 后才支持的新特性
        configurer.addPathPrefix("/api/v1", clazz -> clazz.isAssignableFrom(HelloController.class));

        // 使用 Spring 提供的 HandlerTypePredicate,更加的强大
        // 若添加了两prefix都可以作用在某个Controller上,那么会按照放入的顺序(因为它是LinkedHashMap)以先匹配上的为准
        HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackage("com");
        // HandlerTypePredicate predicate = HandlerTypePredicate.forBasePackageClass(HelloController.class);
        // HandlerTypePredicate predicate = HandlerTypePredicate.forAssignableType(...);
        // HandlerTypePredicate predicate = HandlerTypePredicate.forAnnotation(...);
        // HandlerTypePredicate predicate = HandlerTypePredicate.builder()
        //         .basePackage()
        //         .basePackageClass()
        //         .build();
        configurer.addPathPrefix("/api/v2", predicate);
    }
}

RequestMappingHandlerMapping 向容器中注册的时候,检测到实现了 InitializingBean 接口,容器去执行 afterPropertiesSet(),在 afterPropertiesSet() 中完成 Controller 中方法的映射

 

总结

Spring MVC 在启动时会扫描所有的 @RequestMapping 并封装成对应的 RequestMapingInfo。

一个请求过来会与 RequestMapingInfo 进行逐个比较,找到最适合的那个 RequestMapingInfo。

Spring MVC 通过 HandlerMapping 建立起了 Url Pattern 和 Handler 的对应关系,这样任何一个 URL 请求过来时,就可以快速定位一个唯一的 Handler,然后交给其进行处理了

 


https://blog.csdn.net/f641385712/article/details/89766065

https://blog.csdn.net/f641385712/article/details/89810020

posted @ 2019-03-01 13:54  江湖小小白  阅读(345)  评论(0编辑  收藏  举报