天天看點

Spring MVC 初始化源碼(4)—@RequestMapping注解的源碼解析Spring MVC源碼 系列文章1 @RequestMapping注解解析入口2 getCandidateBeanNames擷取候選beanName3 processCandidateBean處理候選bean4 總結

  基于最新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 請求執行流程的源碼深度解析【兩萬字】

文章目錄

  • 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映射系統資料庫

  

MappingRegistry

AbstractHandlerMethodMapping

的内部類,這是一個系統資料庫的實作,用于維護到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學習部落格!