SpringFlux中Request與HandlerMapping關係的形成過程
一、前言
Spring Flux中的核心DispatcherHandler的處理過程分為三步,其中首步就是通過HandlerMapping介面查詢Request所對應的Handler。本文就是通過閱讀原始碼的方式,分析一下HandlerMapping介面的實現者之一——RequestMappingHandlerMapping類,用於處理基於註解的路由策略,把所有用@Controller和@RequestMapping標記的類中的Handler識別出來,以便DispatcherHandler呼叫的。
HandlerMapping介面的另外兩種實現類:1、RouterFunctionMapping用於函式式端點的路由;2、SimpleUrlHandlerMapping用於顯式註冊的URL模式與WebHandler配對。
文章系列
- 關於非阻塞IO:《從時間碎片角度理解阻塞IO模型及非阻塞模型》
- 關於SpringFlux新手入門:《快速上手Spring Flux框架》
- Spring Flux中Request與HandlerMapping關係的形成過程 本文
二、對基於註解的路由控制器的抽象
Spring中基於註解的控制器的使用方法大致如下:
@Controller
public class MyHandler{
@RequestMapping("/")
public String handlerMethod(){
}
}
在Spring WebFlux中,對上述使用方式進行了三層抽象模型。
-
Mapping
- 使用者定義的基於annotation的對映關係
- 該抽象對應的類是:org.springframework.web.reactive.result.method.RequestMappingInfo
- 比如上述例子中的 @RequestMapping(“/”)所代表的對映關係
-
Handler
- 代表控制器的類
- 該抽象對應的類是:java.lang.Class
- 比如上述例子中的MyHandler類
-
Method
- 具體處理對映的方法
- 該抽象對應的類是:java.lang.reflect.Method
- 比如上述例子中的String handlerMethod()方法
基於上述三層抽象模型,進而可以作一些組合。
-
HandlerMethod
- Handler與Method的結合體,Handler(類)與Method(方法)搭配後就成為一個可執行的單元了
-
Mapping vs HandlerMethod
- 把Mapping與HandlerMethod作為字典存起來,就可以根據請求中的關鍵資訊(路徑、頭資訊等)來匹配到Mapping,再根據Mapping找到HandlerMethod,然後執行HandlerMethod,並傳遞隨請求而來的引數。
理解了這個抽象模型後,接下來分析原始碼來理解Spring WebFlux如何處理請求與Handler之間的Mapping關係時,就非常容易了。
HandlerMapping介面及其各實現類負責上述模型的構建與運作。
三、HandlerMapping介面實現的設計模式
HandlerMapping介面實現,採用了”模版方法”這種設計模式。
1層:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware
^
|
2層:AbstractHandlerMethodMapping implements InitializingBean
^
|
3層:RequestMappingInfoHandlerMapping
^
|
4層:RequestMappingHandlerMapping implements EmbeddedValueResolverAware
下面對各層的職責作簡要說明:
-
第1層主要實現了對外提供模型的介面
- 即過載了HandlerMapping介面的”Mono
- 即過載了HandlerMapping介面的”Mono
-
第2層有兩個責任 —— 解析使用者定義的HandlerMethod + 實現對外提供模型介面實現所需的抽象方法
- 通過實現了InitializingBean介面的”void afterPropertiesSet()”方法,解析使用者定義的Handler和Method。
- 實現第1層對外提供模型介面實現所需的抽象方法:”Mono<?> getHandlerInternal(ServerWebExchange exchange)”
- 第3層提供根據請求匹配Mapping模型例項的方法
- 第4層實現一些高層次用到的抽象方法來建立具體的模型例項。
小結一下,就是HandlerMapping介面及其實現類,把使用者定義的各Controller等,抽象為上述的Mapping、Handler及Method模型,並將Mapping與HandlerMethod作為字典關係存起來,還提供通過匹配請求來獲得HandlerMethod的公共方法。
接下來的章節,將先分析解析使用者定義的模型並快取模型的過程,然後再分析一下匹配請求來獲得HandlerMethod的公共方法的過程。
四、解析使用者定義的模型並快取模型的過程
4-1、實現InitializingBean介面
第2層AbstractHandlerMethodMapping抽象類中的一個重要方法——實現了InitializingBean介面的”void afterPropertiesSet()”方法,為Spring WebFlux帶來了解析使用者定義的模型並快取模型的機會 —— Spring容器初初始化完成該類的具體類的Bean後,將會回撥這個方法。
在該方法中,實現獲取使用者定義的Handler、Method、Mapping以及快取Mapping與HandlerMethod對映關係的功能。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
// Total includes detected mappings + explicit registrations via registerMapping..
...
}
4-2、找到使用者定義的Handler
afterPropertiesSet方法中主要是呼叫了void initHandlerMethods()方法,具體如下:
protected void initHandlerMethods() {
//獲取Spring容器中所有Bean名字
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
//獲取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);
}
}
//如果獲取到型別,並且型別是Handler,則繼續載入Handler方法。
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
//初始化後收尾工作
handlerMethodsInitialized(getHandlerMethods());
}
這兒首先獲取Spring容器中所有Bean名字,然後迴圈處理每一個Bean。如果Bean名稱不是以SCOPED_TARGET_NAME_PREFIX常量開頭,則獲取Bean的型別。如果獲取到型別,並且型別是Handler,則繼續載入Handler方法。
isHandler(beanType)呼叫,檢查Bean的型別是否符合handler定義。
AbstractHandlerMethodMapping抽象類中定義的抽象方法”boolean isHandler(Class<?> beanType)”,是由RequestMappingHandlerMapping類實現的。具體實現程式碼如下:
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
不難看出,對於RequestMappingHandlerMapping這個實現類來說,只有擁有@Controller或者@RequestMapping註解的類,才是Handler。(言下之意對於其他實現類來說Handler的定義不同)。
具體handler的定義,在HandlerMapping各實現類來說是不同的,這也是isHandler抽象方法由具體實現類來實現的原因。
4-3、發現Handler的Method
接下來我們要重點看一下”detectHandlerMethods(beanName);”這個方法呼叫。
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//將handlerType轉換為使用者型別(通常等同於被轉換的型別,不過諸如CGLIB生成的子類會被轉換為原始型別)
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//尋找目標型別userType中的Methods,selectMethods方法的第二個引數是lambda表示式,即感興趣的方法的過濾規則
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
//回撥函式metadatalookup將通過controller定義的mapping與手動定義的mapping合併起來
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isTraceEnabled()) {
logger.trace("Mapped " + methods.size() + " handler method(s) for " + userType + ": " + methods);
}
methods.forEach((key, mapping) -> {
//再次核查方法與型別是否匹配
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
//如果是滿足要求的方法,則註冊到全域性的MappingRegistry例項裡
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
首先將引數handler(即外部傳入的BeanName或者BeanType)轉換為Class<?>型別變數handlerType。如果轉換成功,再將handlerType轉換為使用者型別(通常等同於被轉換的型別,不過諸如CGLIB生成的子類會被轉換為原始型別)。接下來獲取該使用者型別裡所有的方法(Method)。迴圈處理每個方法,如果是滿足要求的方法,則註冊到全域性的MappingRegistry例項裡。
4-4、解析Mapping資訊
其中,以下程式碼片段有必要深入探究一下
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
MethodIntrospector.selectMethods方法的呼叫,將會把用@RequestMapping標記的方法篩選出來,並交給第二個引數所定義的MetadataLookup回撥函式將通過controller定義的mapping與手動定義的mapping合併起來。
第二個引數是用lambda表示式傳入的,表示式中將method、userType傳給getMappingForMethod(method, userType)方法。
getMappingForMethod方法在高層次中是抽象方法,具體的是現在第4層RequestMappingHandlerMapping類中實現。在具體實現getMappingForMethod時,會呼叫到RequestMappingHandlerMapping類的下面這個方法。從該方法中,我們可以看到,首先會獲得引數element(即使用者在Controller中定義的方法)的RequestMapping型別的類例項,然後構造代表Mapping抽象模型的RequestmappingInfo型別例項並返回。
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
構造代表Mapping抽象模型的RequestmappingInfo型別例項,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的資訊,包括paths、methods、params、headers、consumers、produces、mappingName,即使用者定義@RequestMapping註解時所設定的可能的引數,都被存在這兒了。擁有了這些資訊,當請求來到時,RequestMappingInfo就可以測試自身是否是處理該請求的人選之一了。
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.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);
}
return builder.options(this.config).build();
}
4-5、快取Mapping與HandlerMethod關係
最後,registerHandlerMethod(handler, invocableMethod, mapping)呼叫將快取HandlerMethod,其中mapping引數是RequestMappingInfo型別的。。
內部呼叫的是MappingRegistry例項的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo型別。
MappingRegistry類維護所有指向Handler Methods的對映,並暴露方法用於查詢對映,同時提供併發控制。
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
......
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
五、匹配請求來獲得HandlerMethod
AbstractHandlerMethodMapping類的“Mono getHandlerInternal(ServerWebExchange exchange)”方法,具體實現了根據請求查詢HandlerMethod的邏輯。
@Override
public Mono<HandlerMethod> getHandlerInternal(ServerWebExchange exchange) {
//獲取讀鎖
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod;
try {
//呼叫其它方法繼續查詢HandlerMethod
handlerMethod = lookupHandlerMethod(exchange);
}
catch (Exception ex) {
return Mono.error(ex);
}
if (handlerMethod != null) {
handlerMethod = handlerMethod.createWithResolvedBean();
}
return Mono.justOrEmpty(handlerMethod);
}
//釋放讀鎖
finally {
this.mappingRegistry.releaseReadLock();
}
}
handlerMethod = lookupHandlerMethod(exchange)呼叫,繼續查詢HandlerMethod。我們繼續看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定義。為方便閱讀,我把註釋也寫在了程式碼裡。
protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
List<Match> matches = new ArrayList<>();
//查詢所有滿足請求的Mapping,並放入列表mathes
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);
if (!matches.isEmpty()) {
//獲取比較器comparator
Comparator<Match> comparator = new MatchComparator(getMappingComparator(exchange));
//使用比較器將列表matches排序
matches.sort(comparator);
//將排在第1位的作為最佳匹配項
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
//將排在第2位的作為次佳匹配項
Match secondBestMatch = matches.get(1);
}
handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange);
}
}
六、總結
理解了Spring WebFlux在獲取對映關係方面的抽象設計模型後,就很容易讀懂程式碼,進而更加理解框架的具體處理方式,在使用框架時做到“知己知彼”。
相關文章
- 銷售定價過程中的銷項稅是否與國家有關係?
- 【學習】= 概念 + 關係 + 關係發生的過程和條件
- 儲存過程、觸發器與事務之間的關係儲存過程觸發器
- create 與 store中的關係
- 智雲通CRM:銷售過程中,沒有關心就沒有關係?
- 專案干係人管理各過程的輸入輸出關係
- Laravel 從 $request 到 $response 的過程解析Laravel
- tcbs_批量儲存過程_輸出引數out與異常的關係儲存過程
- apache中埠與目錄的關係Apache
- 與if的關係
- Java中類與物件的關係與區別Java物件
- 專案風險管理各過程的輸入輸出關係
- 專案整合管理各過程的輸入輸出關係
- 專案成本管理各過程的輸入輸出關係
- Java中類名與檔名的關係Java
- ORACLE包和過程依賴關係測試(轉)Oracle
- 關聯關係與依賴關係的區別
- 記錄一次班級與學生修改為多對多關係的過程
- 趣文:用雞講解技術債務的形成過程
- 關於學習過程中走過的彎路
- 在Struts2中ValueStack、ActionContext、ServletContext、request、session關係分析ContextServletSession
- 儲存過程遇到gc cr request等待儲存過程GC
- 專案質量管理各過程的輸入輸出關係
- 專案溝通管理各過程的輸入輸出關係
- 專案採購管理各過程的輸入輸出關係
- 專案範圍管理各過程的輸入輸出關係
- 專案時間管理各過程的輸入輸出關係
- js中new關鍵字的使用過程JS
- iOS中關於專案中打包ipa的過程iOS
- Scala與Java的關係Java
- Object與Class的關係Object
- sip與openser的關係
- act與zsh的關係
- 看 Lumen 原始碼解析 Request 到 Response 過程原始碼
- 專案人力資源管理各過程的輸入輸出關係
- 研發過程中的文件管理與工具
- 在愛與恨的邊緣--淺論ERP實施過程中客戶關係的處理(轉)
- js 函式中形參與實參的關係JS函式