目錄導航
- 前言
- 回顧 Spring Web MVC
- `org.springframework.web.servlet.HandlerMapping`
- `HandlerMethod` 初始化過程
- `HandlerMethod` 定位過程
- `HandlerMapping` 和 `HandlerAdapter` 區別
- Spring MVC 2.5 之前面向接口編程
- Spring MVC 2.5+ 面向註解驅動編程
- 理解 WebFlux 實現
- `org.springframework.web.reactive.HandlerMapping`
- `org.springframework.web.reactive.HandlerAdapter`
- `org.springframework.web.reactive.DispatcherHandler`
- Java 微服務實現方案
- 後記
前言
前面的章節我們講了REST。本節,繼續微服務專題的內容分享,共計16小節,分別是:
- 微服務專題01-Spring Application
- 微服務專題02-Spring Web MVC 視圖技術
- 微服務專題03-REST
- 微服務專題04-Spring WebFlux 原理
- 微服務專題05-Spring WebFlux 運用
- 微服務專題06-雲原生應用(Cloud Native Applications)
- 微服務專題07-Spring Cloud 配置管理
- 微服務專題08-Spring Cloud 服務發現
- 微服務專題09-Spring Cloud 負載均衡
- 微服務專題10-Spring Cloud 服務熔斷
- 微服務專題11-Spring Cloud 服務調用
- 微服務專題12-Spring Cloud Gateway
- 微服務專題13-Spring Cloud Stream (上)
- 微服務專題14-Spring Cloud Bus
- 微服務專題15-Spring Cloud Stream 實現
- 微服務專題16-Spring Cloud 整體回顧
本節內容重點爲:
- WebFlux 核心組件:包括
HandlerMapping
、HandlerAdapter
以及HandlerResultHandler
- WebFlux 編程模型運用:介紹 Annotation 驅動以及函數聲明是編程模型的差異以及最佳實踐
回顧 Spring Web MVC
Spring Web MVC是基於Servlet API構建的原始Web框架,並從一開始就包含在Spring Framework中。正式名稱“ Spring Web MVC”來自其源模塊spring-webmvc的名稱, 但它通常被稱爲“ Spring MVC”。
DispatcherServlet特殊bean 的委託來處理請求並提供適當的響應。“特殊bean”是指實現WebFlux框架協定的Spring管理的Object實例。這些通常帶有內置合同,但是您可以自定義它們的屬性,擴展或替換它們。
實際上SpringMVC的很多特性與webflux相同,比如下表是 Spring 官網上列出了通過檢測到的特殊 bean DispatcherHandler:
Bean 的類型 | 說明 |
---|---|
HandlerMapping | 將請求與攔截器列表一起映射到處理程序,以 進行預處理和後期處理。映射基於某些標準,具體細節因HandlerMapping 實現而異。這兩個主要的HandlerMapping 實現是RequestMappingHandlerMapping 支持帶@RequestMapping 註釋的方法,SimpleUrlHandlerMapping 並且維護URI路徑模式向處理程序的顯式註冊。 |
HandlerAdapter | 幫助DispatcherServlet 調用映射到請求的處理程序,而不管實際如何調用該處理程序。例如,調用帶註釋的控制器需要解析註釋。主要目的HandlerAdapter 是保護DispatcherServlet 這些細節。 |
HandlerExceptionResolver | 解決異常的策略,可能將異常映射到處理程序,HTML錯誤視圖或其他。 |
ViewResolver | 解析從處理程序返回到實際的基於邏輯字符串的視圖名稱,View 以呈現給響應。 |
LocaleResolver, LocaleContextResolver | 解決Locale一個客戶正在使用的並且可能是其時區的問題,以便能夠提供國際化的視圖。 |
ThemeResolver | 解決您的Web應用程序可以使用的主題,例如,提供個性化的佈局。 |
MultipartResolver | 藉助多部分解析庫來解析多部分請求的抽象(例如,瀏覽器表單文件上傳)。 |
FlashMapManager | 存儲和檢索“輸入”和“輸出” FlashMap,它們通常用於通過重定向將屬性從一個請求傳遞到另一個請求。 |
除了上述處理類之外,我們再看類 HandlerInterceptor
的源碼,共計三個方法 :
從源碼上不難看出,分爲 前置、後置處理、完成階段(異常處理):
- preHandle 前置
- postHandle 後置
- afterCompletion 完成:類似於
finally
效果。這和java.util.concurrent.CompletableFuture#whenComplete
方法是不是有異曲同工之妙。
org.springframework.web.servlet.HandlerMapping
接下來說一下 HandlerMapping
相關問題:
(上面截圖的的spring版本爲spring-5.0.7-RELEASE) 首先我們看HandlerMapping
的實現類 AbstractHandlerMapping
。
通常來講 HandlerMapping 的實現類是包含 HandlerInterceptor
集合的。那麼在 AbstractHandlerMapping
是否也存在呢,答案是肯定的,請見源碼:
包含 HandlerInterceptor
集合 的意義主要在於:
- 攔截鏈條
- 各司其職
既然是各司其職,就會引發順序問題,誰先執行?誰後執行?
這裏會引出一個概念------- 責任鏈模式
責任鏈通常有兩種類型,即過濾類型和攔截類型。
Q:責任鏈和filter有什麼區別呢?
A:實際上 HandlerInterceptor
就是採用返回值進行流程處理,而 Filter
採用 FilterChain
進行流程處理。 Filter
優先級天生比servlet高,但是 Filter
的最終節點 Servlet。Filter
通過filter.chain進入鏈條的下一個環節,在服務器啓動階段動態組合鏈條,符合責任鏈設計模式(動態調用,組合依賴於配置)。
迴歸主線,繼續分析 HandlerMapping
的實現類 - AbstractHandlerMapping
:
在SpringMVC中,我們知道 DispatcherServlet
關聯多個 HandlerMapping
,類似於笛卡爾積,即:
DispatcherServlet
:HandlerMapping
= 1 : NHandlerMapping
:HandlerInterceptor
= 1 : N
Q: DispatcherServlet
作爲 HandlerMapping
一種,那麼問題來了,多個 HandlerMapping
誰能被 DispatcherServlet
選擇?
這裏我先猜想,上面截圖的源碼 AbstractHandlerMapping
實現了 Ordered
接口參考順序,這是一種可以排序的可能,另外呢,如果 HandlerMapping
被請求規則匹配了是否可以呢?
猜想驗證過程:
首先看一下 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法:
進去看 org.springframework.web.servlet.DispatcherServlet#getHandler:
這裏很明顯是通過循環將所有的handlerMappings 返回出去
所以爲了看到handlerMappings 賦值的過程,我這裏直接通過idea工具右鍵選中handlerMappings選擇find usages菜單,查看讀寫的位置:
通過工具,發現handlerMappings的寫入都集中在類 DispatcherServlet 上,所以直接跟進:
來到方法:org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
initHandlerMappings 方法,主要是初始化HandlerMappings的結果,這裏首先將所有匹配的handlerMapping放入了Map<String,HandlerMapping> 。
注意 initHandlerMappings 方法中這段代碼:
this.handlerMappings = new ArrayList<>(matchingBeans.values());
爲什麼不能將匹配的 handlerMappings直接賦值給當前this變量裏呢,而是新創建一個集合再賦值?
- 首先,Map的values()方法返回的就是Collection
我們看說明,返回值是:a collection view of the values contained in this map。注意是View,也就是說,你只能看看values(這裏實際上是HandlerMappings),數據是不可變的。
- 其次,通過new了一個list數組以後,就可以動態的去寫數據,而能寫數據就可以對HandlerMappings進行排序了。所以緊接着代碼如下:
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
接下來看看 sort 方法如何進行排序:
我們看 AnnotationAwareOrderComparator 的 API 發現如何排序的呢,有兩種方式,分別是:
- 通過某個Bean實現Ordered接口,我們這裏明顯是 AbstractHandlerMapping 去實現 Ordered接口,前面已經兩次提到了!
當實現了Ordered以後,可以通過 getOrder方法返回順序,注意源碼所示:將HIGHEST_PRECEDENCE賦值給Integer的最小值;將LOWEST_PRECEDENCE賦值給Integer的最大值。所以總結:在類Order的規則裏,順序越大,優先級越小,順序越小,優先級越大!
- 通過實現
@Order
註解
在Bean上注入 @Order
註解,這裏沒有應用,略講。
Tips:看源碼就是要考慮很多細節性的問題,一個優秀的架構師,往往能夠做到通用性好,並且思考的更深刻
回過頭來,繼續看主線內容:
前面提到: DispatcherServlet
可以關聯多個 HandlerMapping
,HandlerMapping
也可以關聯多個 HandlerInterceptor
,這種 1 對 N 的特性其實是有條件的!是需要經過篩選的,請看AbstractHandlerMapping
源碼:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//核心代碼
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
此方法前面進行參數判斷,尾部適配瀏覽器標準(跨域訪問),重要看中間代碼
直接看方法getHandlerExecutionChain:
注意這裏:
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
此方法主要對HTTP請求的 URL 地址做處理,舉個栗子:請求地址是 http://www.baidu.com/abc/def,那麼 轉化後剩下 /abc/def,其實就是相對路徑。
接下來對於HandlerInterceptor 集合遍歷,然後篩選,在本節開頭就提到的HandlerInterceptor,它和MappedInterceptor有什麼區別呢?
HandlerInterceptor可以匹配請求,而MappedInterceptor則不可以。
正因爲HandlerInterceptor可以匹配請求,源碼接着往下看,經過chain添加Interceptor纔是被過濾的!
那麼chain是怎麼添加的Interceptor的呢?在回答這個問題之前,我們播放一個小插曲!
在前面的劇情裏,AbstractHandlerMapping 重寫了HandlerMapping
接口的 getHandler, 返回值是 HandlerExecutionChain
,現在我們看此返回值類:
這裏面有兩個集合:
- HandlerInterceptor[]
- List< HandlerInterceptor >
爲什麼要用兩個屬性相同的集合來表示?
插曲播放完畢,讓我們回到剛纔的問題上,剛纔說到的1對N問題,我們說是要經過篩選的!是通過chain去添加Interceptor的,那麼怎麼添加的呢?
我們將
chain.addInterceptor(mappedInterceptor.getInterceptor());
這段代碼點擊實現裏看看:
private List<HandlerInterceptor> initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<>();
if (this.interceptors != null) {
// An interceptor array specified through the constructor
CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);
}
}
this.interceptors = null;
return this.interceptorList;
}
這裏的實現是通過interceptors數組作爲臨時變量存儲後merge到interceptorList集合裏,然後清空interceptors數組。interceptors數組起到臨時變量的作用。
問題來了,爲什麼要臨時存儲interceptor 集合呢?我們可以看看此集合在哪裏被用到了便一目瞭然
老辦法,對準臨時變量interceptors就是操作find usages:
我們找到這裏:
list是可以變化的,而數組是沒法變化的,寫兩種集合,好處就在於:
- 爲了防止串數據,改動一方不影響另一方。
- 如果不改的話,考慮到框架前後版本不一致問題,某些版本訪問是數組,也可能是集合,一旦寫死就很麻煩~
最後我們再看類HandlerMapping 的 getHandler方法進行分析,getHandler的返回值是HandlerExecutionChain:
public class HandlerExecutionChain {
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
...
前面對於兩個集合分析了一遍,那麼看看這個Object類型的handler指的又是什麼呢?
請參考本節開始的截圖的HandlerMapping
類圖:我們根據類圖層級調用關係可以猜測到:
當一個request請求來臨時,一定是通過handlerMapping
抽象實現類AbstractHandlerMapping
或者CompositeHandlerMapping
去處理請求返回handler的,那麼CompositeHandlerMapping
實爲透傳,我們將重點放在AbstractHandlerMapping
類的getHandler:
我們看着一段代碼:
Object handler = getHandlerInternal(request);
接着看getHandlerInternal:
後兩個方法實爲透傳,我們分別看前兩個實現類:
AbstractHandlerMethodMapping
AbstractHandlerMethodMapping
通過其實現類, RequestMappingInfoHandlerMapping
,再調用實現類 RequestMappingHandlerMapping
返回 HandlerMethod
。
AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
獲取 Handler(注意返回類型),在通過其實現類 SimpleUrlHandlerMapping
返回 Object
。
Tips: 通過這一小段分析,我們知道handler的類型是不固定的,通過
SimpleUrlHandlerMapping
處理後返回的handler類型是Object,而通過RequestMappingHandlerMapping
返回的的handelr類型是HandlerMethod
。
那麼 HandlerMethod
與 執行方法有什麼聯繫呢?
HandlerMethod
初始化過程
如果當
@Controller
方法上面標註了@RequestMapping
,而@RequestMapping
主要可以標識URI。比如@RequestMapping(“/cache”)。所以我們就可以大膽猜測是通過RequestMappingHandlerMapping
處理的。
Q:那麼源碼上對於 HandlerMethod
與 執行方法是怎麼處理的呢?
A:我們從 HandlerMethod
初始化的地方入手看 org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping#afterPropertiesSet:
afterPropertiesSet方法會在父類的Bean(RequestMappingHandlerMapping)初始化的時候調用。
我們看這裏:
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
如果beanType 不爲空,並且符合isHandler條件的,才能夠進入裏面的方法,所以我們先看看,什麼是所謂的Handler呢?
就是說當前bean被標註爲@Controller
或者@RequestMapping
註解的纔會被掃描到。
那麼
@RestController
以及@PostMapping
等註解會被掃描到麼?答案是肯定的,還記得我們前面的小節講到的註解的派生性麼?可以簡單的理解爲子類與父類的關係,只是功能上等同哦,所以這裏掃描@Controller
或者@RequestMapping
的父類是可以將子類掃描進去的,框架本身也更好的在註解層面實現了擴展。
然後看方法detectHandlerMethods,根據字面意思是,探測所有的HandlerMethods,所以進一步查看:
此方法首先求Bean,然後把Bean裏的Method都執行一遍,註冊HandlerMethod,進去看註冊的過程:
透傳參數,繼續調用查看register方法:
由上述過程,可以總結一下HandlerMethod
初始化過程:
- Spring 應用上下文獲取所有的 Bean
- 篩選出標註
@Controller
或者@RequestMapping
的 Bean - 再次篩選標準
@RequestMapping
方法 - 將該 Bean 和對應
Method
合併成HandlerMethod
- 存入
HandlerMethod
集合
HandlerMethod
定位過程
我們知道當一個Request請求進來時,要先經過 DispatchServlet 的doDispatch方法:
如何獲取到的Handler呢?接着看源碼的調用:
這裏基本上相當於透傳,接着往裏看:
這不又是回到了最初的起點麼?
我們通過前面的分析已經知道,實際從Request裏解析的是:AbstractHandlerMapping
,而AbstractHandlerMapping
又是通過另外兩個子類實現不同的返回值類型的Handler,這裏我們前面已經提到。
這裏以AbstractHandlerMehtodMapping
爲切入點:我們看獲取HandlerMethod
時這段代碼:
基本上分爲三個部分,前面判斷如果沒有MatchingMappings,則從所有的mapping集合裏取出來:
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
接下來是匹配合適的Handler最後將其返回。
由上述過程,可以總結一下HandlerMethod
定位過程:
HandlerMethod
集合查找@RequestMapping
定義的 URI- 返回
HandlerMethod
HandlerMapping
和 HandlerAdapter
區別
從DispatchServlet類的屬性上我們看到,實際上1對多的關係有很多:
這裏講一下 HandlerMapping
和 HandlerAdapter
區別:
HandlerMapping
主要負責映射,通過一次 HTTP 請求找到對應(最佳匹配規則)的 HandlerMethod
以及多個 HandlerInterceptor
,而 HandlerInterceptor
也等同與 HandlerExecutionChain
,看一下此類的基本屬性:
HandlerExecutionChain
這熟悉的三件套和我們之前分析AbstractHandlerMapping
不也是異曲同工麼!
而 HandlerAdapter
主要負責 Handler 執行後處理,詳細邏輯見org.springframework.web.servlet.DispatcherServlet#getHandler:
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
HandlerMapping 的getHandler處理後返回HandlerExecutionChain。
HandlerInterceptor
: 沒有匹配請求,而MappedInterceptor
: 能夠匹配請求。
Spring MVC 2.5 之前面向接口編程
在老版本的頁面渲染處理中, HTML頁面渲染採用 JstlView
, JSON渲染則採用 MappingJackson2JsonView
。
並且我們看Controller 接口定義:
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
其方法返回值 ModelAndView
,方法參數 HttpServletRequest
和 HttpServletResponse
。
HandlerMapping
老實現:
Spring MVC 2.5+ 面向註解驅動編程
通常我們說
View
是來做頁面渲染,同樣的JstlView
也是對其做的實現,但是,實際場景中。@Controller
處理方法可能不返回ModelAndView
,這種情況下怎麼辦呢?
這就是SpringMVC新版本提出的特性:就是通過 HandlerMethod
對於 ModelAndView
進行適配。
到了Spring Web MVC 2.5+ 以後,則引入了適配器(Adapter)與HandlerMethod的概念。前面我們也分析了HandlerMethod流轉過程,那麼HandlerMethod作爲形參有什麼樣的優勢呢?
HandlerMethod
返回值:ModelAndView
String
ResponseEntity
void
HandlerMethod
參數:@RequestParam
@RequestHeader
@PathVariable
@RequestBody
Model
HandlerMapping
新實現:RequestMappingHandlerMapping
這些參數的擴展相對於老版本不是更加靈活了麼!
理解 WebFlux 實現
org.springframework.web.reactive.HandlerMapping
對比參考 org.springframework.web.servlet.HandlerMapping
org.springframework.web.reactive.HandlerAdapter
對比參考 org.springframework.web.servlet.HandlerAdapter
org.springframework.web.reactive.DispatcherHandler
對比參考 org.springframework.web.servlet.DispatcherServlet
Java 微服務實現方案
Vert.x
Spring Boot / Spring Cloud
https://spring.io/projects/spring-cloud
後記
更多架構知識,歡迎關注本套Java系列文章:Java架構師成長之路