Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解

2021/6/20 11:50:25

本文主要是介绍Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

  基于最新Spring 5.x,详细介绍了Spring MVC中的@RequestMapping注解解析的源码。

  我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442。

  采用@RequestMapping注解以及使用@RequestMapping作为元注解的注解修饰方法来实现的Controller控制器将被解析为HandlerMethod类型的Handler处理器对象,该对象保存着URI路径到对应控制器方法的映射关系,后面请求的时候会根据路径找到Handler,然后拿到Handler对应的控制器方法直接执行,而无需再次解析。

  下面让我一起来看看基于@RequestMapping注解的Controller控制器的解析流程源码。下面的源码版本基于5.2.8.RELEASE

Spring MVC源码 系列文章

Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化

Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析

Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析

文章目录

  • Spring MVC源码 系列文章
  • 1 @RequestMapping注解解析入口
  • 2 getCandidateBeanNames获取候选beanName
  • 3 processCandidateBean处理候选bean
    • 3.1 isHandler是否是handler类
    • 3.2 detectHandlerMethods解析HandlerMethod
      • 3.2.1 getMappingForMethod获取RequestMappingInfo映射
        • 3.2.1.1 createRequestMappingInfo创建RequestMappingInfo
        • 3.2.1.2 combine合并属性
        • 3.2.1.3 pathPrefixes路径前缀
      • 3.2.2 registerHandlerMethod注册HandlerMethod
        • 3.2.2.1 MappingRegistry映射注册表
  • 4 总结

1 @RequestMapping注解解析入口

  通常Handler处理器在容器启动的时候就被解析了,因此HandlerMethod也在启动时解析的,RequestMappingHandlerMapping实现了InitializingBean接口,在RequestMappingHandlerMapping被实例化之后的将会触发它的InitializingBean#afterPropertiesSet方法回调,在该方法中就会进行基于注解的控制器方法的解析。
  下面来看看基于@RequestMapping注解的方法控制器的解析原理。

/**
 * RequestMappingHandlerMapping的属性
 * <p>
 * 用于请求映射目的的配置选项的容器。
 * 这种配置是创建RequestMappingInfo实例所必需的,通常在所有RequestMappingInfo实例创建中使用。
 */
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();


/**
 * RequestMappingHandlerMapping的方法
 */
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
    //将一系列属性设置到RequestMappingInfo.BuilderConfiguration中
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(useSuffixPatternMatch());
    this.config.setTrailingSlashMatch(useTrailingSlashMatch());
    this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    /*调用父类AbstractHandlerMethodMapping的方法*/
    super.afterPropertiesSet();
}

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 在初始化时立即检测处理器方法。
 */
@Override
public void afterPropertiesSet() {
    //调用initHandlerMethods方法
    initHandlerMethods();
}

  initHandlerMethods方法用于在ApplicationContext中扫描bean,检测并注册Handler方法。

/**
 * 使用了scoped-proxy作用域代理的Bean名称前缀,前缀之后就是原始beanName
 */
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 在ApplicationContext中扫描bean,检测并注册Handler方法。
 */
protected void initHandlerMethods() {
    //getCandidateBeanNames()实际上会获取当前Spring MVC容器中所有bean的beanName,默认不会获取父Spring容器中的beanName
    //因此建议对不同类型的bean定义在不同的容器中,这样这里的循环遍历判断的时间就会有效减少
    for (String beanName : getCandidateBeanNames()) {
        //如果beanName不是以"scopedTarget."开头的,即不是作用域代理bean,那么处理此bean
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            //处理候选bean.确定指定的候选bean的类型,如果是handler类型
            //那么调用detectHandlerMethods方法在指定的 handler bean中查找handler方法
            //并注册一系列缓存到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中(该属性对象内部的数据集合中)。
            processCandidateBean(beanName);
        }
    }
    //在检测到所有handler method之后调用,方法参数是mappingLookup缓存
    //目前版本中该方法主要目的仅仅是尝试打印日志信息,比如handlerMethods的总数
    handlerMethodsInitialized(getHandlerMethods());
}

  可以看到,initHandlerMethods方法将会获取当前Spring MVC容器中所有bean的beanName作为候选bean,默认不会获取父Spring容器中的beanName。
  随后对每一个候选bean循环调用processCandidateBean方法,该方法中将确定指定的候选bean的类型,如果是handler bean,那么调用detectHandlerMethods方法在指定的 handler bean中查找handler方法,并注册一系列缓存到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中(该属性对象内部的缓存集合中)。

2 getCandidateBeanNames获取候选beanName

  getCandidateBeanNames()实际上会获取当前Spring MVC容器中所有bean的beanName,默认不会获取父Spring容器中的beanName。因此建议对不同类型的bean定义在不同的容器中,这样这里的循环遍历判断的时间就会有效减少
  这个标志很重要,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器中的Handler方法。因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。这里的原理源码。

/**
 * AbstractHandlerMethodMapping的属性
 * <p>
 * 是否在祖先ApplicationContexts中的Bean中检测处理器方法。
 * 默认值为“ false”:仅考虑当前ApplicationContext中的bean,即仅在定义了此HandlerMapping本身的上下文中(通常是当前DispatcherServlet的上下文)。
 * 将此标志也打开,以检测祖先上下文(通常是Spring Root WebApplicationContext)中的处理程序bean。
 * <p>
 * 这个标志很重要,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器中的Handler方法
 * 因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。
 */
private boolean detectHandlerMethodsInAncestorContexts = false;

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 在应用程序上下文中确定候选bean的名称。
 *
 * @return 候选bean的名称数组,默认是容器中的全部beanName
 */
protected String[] getCandidateBeanNames() {
    //是否在祖先ApplicationContexts中的Bean中检测处理器方法,默认false,将仅在当前DispatcherServlet的上下文检测
    //这里是尝试获取Object类型的beanName数组,由于类型是Object因此将会获取所有的注册的beanName
    return (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
            obtainApplicationContext().getBeanNamesForType(Object.class));
}

3 processCandidateBean处理候选bean

  确定指定的候选bean的类型,如果是handler类型,那么调用detectHandlerMethods方法在在指定的 handler bean中查找handler方法,并注册一系列缓存到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中(该属性对象内部的数据集合中)。

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 确定指定的候选bean的类型,如果是handler类型,那么调用detectHandlerMethods方法
 * <p>
 * 此实现通过检查org.springframework.beans.factory.BeanFactory#getType
 * 并使用bean名称调用detectHandlerMethods方法来避免bean创建。
 *
 * @param beanName 候选bean的名称
 */
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        //根据beanName获取bean类型
        beanType = obtainApplicationContext().getType(beanName);
    } catch (Throwable ex) {
        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
        if (logger.isTraceEnabled()) {
            logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
        }
    }
    //如果isHandler方法返回true,即当前类属于handler类
    if (beanType != null && isHandler(beanType)) {
        //那么在指定的handler bean中查找handler方法
        //并注册到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中
        detectHandlerMethods(beanName);
    }
}

3.1 isHandler是否是handler类

  该方法判断给定类型是否是一个handler类,期望处理器类具有类级别的@Controller注解或类型级别的@RequestMapping注解,也就是说bean的类上如果具有这两个注解之一,那么该类就是handler类型。

/**
 * RequestMappingHandlerMapping的方法
 * <p>
 * 给定类型是否是一个handler类
 * <p>
 * 期望处理器类具有类级别的@Controller注解或类型级别的@RequestMapping注解
 * 如果具有这两个注解之一,那么该类就是handler类型
 *
 * @param beanType 被检查的bean的类型
 * @return 如果是handler类型,则为“ true”,否则为“ false”。
 */
@Override
protected boolean isHandler(Class<?> beanType) {
    //类上是否具有@Controller或者@RequestMapping注解
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

3.2 detectHandlerMethods解析HandlerMethod

  在指定的handler bean中查找handler方法,并注册到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中。

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 在指定的handler bean中查找handler方法并注册到缓存中
 *
 * @param handler Bean名称或实际handler实例
 */
protected void detectHandlerMethods(Object handler) {
    //获取handler的类型
    Class<?> handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        //查找该类的方法,并解析方法和类上的@RequestMapping注解创建RequestMappingInfo对象,T为RequestMappingInfo类型的实例
        //最终返回一个map,每个方法都有对应的RequestMappingInfo实例,如果该方法上没有@RequestMapping注解,则value为null
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        /*
                         * 1 查找该方法对应的RequestMappingInfo映射
                         *
                         * 该方法由子类实现,对于RequestMappingHandlerMapping,实际上就是
                         * 解析方法上的@RequestMapping注解并创建一个RequestMappingInfo对象
                         * 即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null
                         */
                        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));
        }
        //遍历获取到的map
        methods.forEach((method, mapping) -> {
            //根据对象的类型(可能是代理对象类型),解析当前方法成为一个真正可执行方法
            //为什么要这么做?因为实际执行该方法的对象可能是一个代理对象,进而在调用该方法时抛出各种异常
            //这个方法我们在Spring事件发布机制的源码文章中就详细讲解过了,在此不再赘述
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            /*
             * 2 注册handlerMethod的一系列映射关系
             */
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

3.2.1 getMappingForMethod获取RequestMappingInfo映射

  解析方法和类级别的@RequestMapping注解(或者以该注解为元注解的注解),创建该handler方法的RequestMappingInfo映射。如果方法没有@RequestMapping注解,则获取null。
  该方法由子类实现,对于RequestMappingHandlerMapping,实际上就是解析方法上的@RequestMapping注解并创建一个RequestMappingInfo对象,即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null。
  如果在类和方法上都存在@RequestMapping注解。那么最终的RequestMappingInfo将会并两个注解的属性,比如对于该handler的访问URI,一般就是类上的注解的path路径位于方法上的注解的path路径之前。

/**
 * RequestMappingHandlerMapping的方法
 * <p>
 * 解析方法和类级别的@RequestMapping注解(或者以该注解为元注解的注解)创建该handler方法的RequestMappingInfo映射。
 *
 * @param handlerType handler类的CLass
 * @param method      handler类的某个Method
 * @return 创建的RequestMappingInfo;如果方法没有@RequestMapping注解,则为null。
 */
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    //解析方法上的@RequestMapping注解并创建一个RequestMappingInfo对象
    //即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        //解析类上的@RequestMapping注解并创建一个RequestMappingInfo对象
        //即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        //如果类上存在@RequestMapping注解注解
        if (typeInfo != null) {
            //那么类上的注解信息和方法上的注解信息联合起来,也就是合并一些属性
            //对于path路径属性的联合,主要就是在方法的注解path前面加上类上的注解的path路径
            info = typeInfo.combine(info);
        }
        //获取该handler的额外访问URI路径前缀,一般为null
        //但我们可以手动配置RequestMappingHandlerMapping的pathPrefixes属性
        String prefix = getPathPrefix(handlerType);
        //如果存在对该handler的URI前缀
        if (prefix != null) {
            //则在path前面继续加上路径前缀,后续访问时可以加上前缀路径,也可以不加
            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}

3.2.1.1 createRequestMappingInfo创建RequestMappingInfo

  该方法查找方法/类上的@RequestMapping注解并创建一个RequestMappingInfo构建者模式。该注解可以是直接声明的注解,元注解,也可以是在注解层次结构中合并注解属性的综合结果(即这个注解可以来自于父类)。

/**
 * RequestMappingHandlerMapping的方法
 * <p>
 * 委托createRequestMappingInfo(RequestMapping,RequestCondition)
 * 根据所提供的annotatedElement是类还是方法来提供适当的自定义RequestCondition。
 *
 * @param element 需要创建RequestMappingInfo的源数据,可能是方法或者类
 */
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    //获取方法/类上的@RequestMapping注解,支持从父类的方法/类上查找
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    //处理该请求的条件,这个版本无论是方法还是类都返回null
    RequestCondition<?> condition = (element instanceof Class ?
            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    //通过方法上的@RequestMapping注解以及RequestCondition创建一个RequestMappingInfo,
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}


/**
 * RequestMappingHandlerMapping的方法
 * <p>
 * 通过方法/类上的@RequestMapping注解创建一个RequestMappingInfo,构建者模式。
 * 该注解可以是直接声明的注解,元注解,也可以是在注解层次结构中合并注解属性的综合结果。
 *
 * @param customCondition 自定义的条件
 * @param requestMapping  注解
 */
protected RequestMappingInfo createRequestMappingInfo(
        RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
    RequestMappingInfo.Builder builder = RequestMappingInfo
            //解析注解的value和path属性,即请求的URI路径,这是一个数组
            //path路径支持${..:..}占位符,并且支持普通方式从外部配置文件中加载进来的属性以及environment的属性。
            //path路径还支持SPEL表达式(首先解析占位符,然后解析SPEL表达式)
            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
            //解析注解的method属性,即请求方法,这是一个数组
            .methods(requestMapping.method())
            //解析注解的params属性,即请求参数匹配,这是一个数组
            .params(requestMapping.params())
            //解析注解的headers属性,即请求头匹配,这是一个数组
            .headers(requestMapping.headers())
            //解析注解的consumes属性,即请求的Content-Type匹配,这是一个数组
            .consumes(requestMapping.consumes())
            //解析注解的produces属性,即请求的Accept匹配,这是一个数组
            .produces(requestMapping.produces())
            //解析注解的name属性,即映射名称
            .mappingName(requestMapping.name());
    if (customCondition != null) {
        builder.customCondition(customCondition);
    }
    //构建RequestMappingInfo
    return builder.options(this.config).build();
}

3.2.1.2 combine合并属性

  如果方法和类上都有@RequestMapping注解,那么对于俩个注解的属性执行属性合并操作,最终返回一个新的RequestMappingInfo对象。

/**
 * RequestMappingInfo的方法
 * <p>
 * 将“此”请求映射信息(即当前实例)与另一个请求映射信息实例结合起来。
 *
 * @param other 另一个请求映射信息实例.(即方法上的注解解析的RequestMappingInfo)
 * @return 一个新的请求映射信息实例,永不为null
 */
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
    //name属性,只有一个注解具有该属性,那么就是使用该注解设置的属性
    //如果两个注解都具有该属性,那么使用"#"联合这两个属性,类注解属性在前
    String name = combineNames(other);
    //合并PatternsRequestCondition,最主要就是合并两个注解的path属性,即URI路径
    //原理比较复杂,将会对两个注解的path数组对应的索引的值进行合并
    //如果只有一个注解具有该属性,那么就是使用该注解设置的属性
    //如果两个注解都具有该属性,那么要分情况讨论,比较复杂,比如路径通配符,后缀通配符等
    //最常见的情况就是在方法的注解path前面加上类上的注解的path路径,它们之间使用"/"分隔
    PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
    //合并methods、Params、headers属性,如果只有一个注解具有该属性,那么就是使用该注解设置的属性
    //否则最终合并的结果将是两个注解的属性直接合并之后的结果(去重)
    RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
    ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
    HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
    //合并consumes、produces属性,如果参数注解(方法上的注解)具有该属性,那么就使用参数注解的属性,否则使用当前注解的属性(类注解的属性)
    ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
    ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
    //合并customCondition属性,一般都为null
    RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

    //根据合并的属性创建一个新的RequestMappingInfo
    return new RequestMappingInfo(name, patterns,
            methods, params, headers, consumes, produces, custom.getCondition());
}

3.2.1.3 pathPrefixes路径前缀

  自Spring5.1开始,支持为某个handler类下面的所有handler方法配置URI路径前缀,在请求的时候就可以在path之前加上访加上前缀路径,当前也可以不加。
  前缀路径通过RequestMappingHandlerMapping的pathPrefixes属性来配置,该属性是一个LinkedHashMap。key为前缀路径字符串,value为对应的Predicate断言,如果某个handler的Class满足Predicate,那么就可以使用key作为路经前缀。
  在断言的时候,如果因此如果有多个能够匹配的Predicate,由于是一次匹配,并且集合是LinkedHashMap,那么配置在前面的路径前缀将被使用。

//RequestMappingHandlerMapping的属性

/**
 * SPring 5.1新增的路径前缀属性map
 * <p>
 * key为前缀路径字符串,value为对应的Predicate断言,如果某个handler的Class满足Predicate,那么就可以使用key作为路经前缀
 */
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();

/**
 * 用于执行${..:..}占位符匹配和SPEL表达式解析
 */
@Nullable
private StringValueResolver embeddedValueResolver;

/**
 * RequestMappingHandlerMapping的方法
 * <p>
 * 获取当前handler类的路径前缀
 *
 * @param handlerType 当前handler类的Class
 * @return 前缀路径,没有则返回null
 */
@Nullable
String getPathPrefix(Class<?> handlerType) {
    //遍历map依次匹配,因此如果有多个能够匹配的Predicate,那么配置在前面的最先匹配
    for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
        //执行Predicate的test方法尝试断言,如果可以匹配该Class,那么获取key作为路径前缀
        if (entry.getValue().test(handlerType)) {
            String prefix = entry.getKey();
            //前缀路径字符串还支持${..:..}占位符匹配和SPEL表达式
            if (this.embeddedValueResolver != null) {
                prefix = this.embeddedValueResolver.resolveStringValue(prefix);
            }
            return prefix;
        }
    }
    return null;
}

3.2.2 registerHandlerMethod注册HandlerMethod

  注册handler方法及其唯一映射的一系列对应关系,在项目启动时会为每个检测到的handler方法调用该方法,后续请求到来的时候可以直接从注册表中获取并使用,无需再次解析。

/**
 * RequestMappingHandlerMapping的方法
 *
 * @param handler handler的beanName或者handler实例
 * @param method 注册方法
 * @param mapping 与handler方法关联的映射条件RequestMappingInfo
 */
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    //调用父类AbstractHandlerMethodMapping的同名方法
    super.registerHandlerMethod(handler, method, mapping);
    //更新ConsumesCondition的bodyRequired属性,该属性从方法参数上的@RequestBody注解的required属性中获取。
    updateConsumesCondition(mapping, method);
}

  其内部首先调用父类AbstractHandlerMethodMapping的同名方法,该方法真正的实现HandlerMethod的注册。

/**
 * AbstractHandlerMethodMapping的属性
 * <p>
 * handler方法映射注册表
 */
private final MappingRegistry mappingRegistry = new MappingRegistry();

/**
 * AbstractHandlerMethodMapping的方法
 * <p>
 * 注册handler方法及其唯一映射的对应关系,在项目启动时会为每个检测到的handler方法调用该方法。
 *
 * @param handler handler的beanName或者handler实例
 * @param method  注册方法
 * @param mapping 与handler方法关联的映射条件RequestMappingInfo
 * @throws IllegalStateException 如果已经在同一映射下注册了另一种方法
 */
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    //调用mappingRegistry的register方法
    this.mappingRegistry.register(mapping, handler, method);
}

3.2.2.1 MappingRegistry映射注册表

  MappingRegistryAbstractHandlerMethodMapping的内部类,这是一个注册表的实现,用于维护到handler method的所有映射缓存。
  核心方法就是register方法。

/**
 * AbstractHandlerMethodMapping的内部类
 * <p>
 * 一个注册表,用于维护到handler method的所有映射缓存。
 */
class MappingRegistry {

    /**
     * RequestMappingInfo到MappingRegistration的缓存
     */
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

    /**
     * RequestMappingInfo到HandlerMethod的缓存
     */
    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

    /**
     * url路径到RequestMappingInfo的缓存,url可以存在通配符等特殊字符
     * 这里的value可以是多个RequestMappingInfo,因为一个url路径可能对应多个RequestMappingInfo
     * 它们使用请求方法来区分,因此使用MultiValueMap。
     */
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

    /**
     * mapping name到多个HandlerMethod的缓存
     */
    private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

    /**
     * HandlerMethod到CorsConfiguration的缓存,用于Cors跨域的处理
     */
    private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

    /**
     * 一个读写锁,提升效率
     */
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 返回所有mapping和handler方法。
     */
    public Map<T, HandlerMethod> getMappings() {
        return this.mappingLookup;
    }

    /**
     * 返回给定URL路径的匹配mapping
     */
    @Nullable
    public List<T> getMappingsByUrl(String urlPath) {
        return this.urlLookup.get(urlPath);
    }

    /**
     * 通过mapping name返回handler method。
     */
    public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
        return this.nameLookup.get(mappingName);
    }

    /**
     * 返回给定handlerMethod的CORS配置
     */
    @Nullable
    public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
        HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
        return this.corsLookup.get(original != null ? original : handlerMethod);
    }

    /**
     * 调用getMappings和getMappingsByUrl方法之前获取读锁
     */
    public void acquireReadLock() {
        this.readWriteLock.readLock().lock();
    }

    /**
     * 调用getMappings和getMappingsByUrl方法之后释放读锁
     */
    public void releaseReadLock() {
        this.readWriteLock.readLock().unlock();
    }

    /**
     * 注册Mapping、handler、Method之间的一系列映射缓存,将会使用写锁保证线程安全
     *
     * @param mapping 映射,对于RequestMappingHandlerMapping来说就是RequestMappingInfo
     * @param handler handler类的beanName或者handler实例
     * @param method  handler的某个方法
     */
    public void register(T mapping, Object handler, Method method) {
        //Kotlin语言支持
        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 {
            //根据handler和method创建HandlerMethod,实际上就是new一个新的HandlerMethod对象,该对象内部保存了
            //bean(beanName/bean实例)、beanType、method(给定的方法)、parameters(方法参数)等等属性
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            //检验mappingLookup,因为一个mapping只能对应一个handlerMethod
            //如果当前mapping对应了多个HandlerMethod,则会抛出异常:Ambiguous mapping. Cannot map
            validateMethodMapping(handlerMethod, mapping);
            /*mapping和handlerMethod存入mappingLookup缓存*/
            this.mappingLookup.put(mapping, handlerMethod);
            /*url路径和mapping的关系存入urlLookup缓存*/
            List<String> directUrls = getDirectUrls(mapping);
            for (String url : directUrls) {
                this.urlLookup.add(url, mapping);
            }
            /*name和mapping的关系存入nameLookup缓存*/
            String name = null;
            if (getNamingStrategy() != null) {
                name = getNamingStrategy().getName(handlerMethod, mapping);
                addMappingName(name, handlerMethod);
            }

            /*
             * 初始化当前方法的Cors跨域属性配置类
             * 会尝试获取当前方法以及方法所属的类上的@CrossOrigin注解,并会对他们进行合并
             */
            CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
            if (corsConfig != null) {
                /*handlerMethod和corsConfig存入corsLookup缓存*/
                this.corsLookup.put(handlerMethod, corsConfig);
            }
            /*
             * mapping和MappingRegistration存入mappingLookup缓存
             * MappingRegistration对象相当于其他属性的一个集合,从该对象可以获取当前映射对应的的各种信息
             * 比如mapping、handlerMethod、directUrls、name
             */
            this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
        } finally {
            //释放写锁
            this.readWriteLock.writeLock().unlock();
        }
    }

    private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {
        // 获取该mapping对应的HandlerMethod
        HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping);
        //如果existingHandlerMethod不为null,并且缓存中的handlerMethod和新建的handlerMethod不相等,那么抛出异常
        if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) {
            throw new IllegalStateException(
                    "Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" +
                            handlerMethod + "\nto " + mapping + ": There is already '" +
                            existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped.");
        }
    }

    //……省略其他方法
}

4 总结

  基于@RequestMapping注解的Handler方法在Spring MVC容器启动的时候就会被解析并缓存起来,将会被解析为HandlerMethod对象,后续请求进来的时候通过HandlerMethod可以找到某个请求到具体的handler方法的映射关系,无需再次解析。
  基于@RequestMapping注解的Handler方法在解析时,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器中的Handler方法。因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。但是可以通过设置detectHandlerMethodsInAncestorContexts 属性为true来表示启动父容器中的基于@RequestMapping注解的Handler方法的解析。

相关文章:
  https://spring.io/
  Spring Framework 5.x 学习
  Spring MVC 5.x 学习
  Spring Framework 5.x 源码

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!



这篇关于Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程