基于最新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學習部落格!