今天很難,明天更難,後天也許更難
文章目錄
回顧
上期說道,HandlerAdapter
存在的意義,就是充當request,reponse
與我們定義的各種形式handler
之間的參數適配,返回值適配。
當我們在享受着SpringMVC帶來的多樣化參數接收形式,以及簡便的返回值操作時,殊不知,HandlerAdapter
在背後默默的爲我們奉獻着。
RequestMappingHandlerAdapter
適用於@RequestMapping
註解標註的Handler
,是處理我們定義的controller
接口最重要的HandlerAdapter
。基於他的重要地位,本文講講他是如何工作的。
1.初始化
先從其初始化開始下手
爲了應對handler的各種樣式的參數接收,在handlerApater
裏,就需要大量的轉換工具來類處理這些東西。
所以:初始化的過程中,就是要設置各種參數工具,返回值處理工具等等,這些工具可以是Spring
提供的或者開發人員自己定義的。
(1)
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
public void afterPropertiesSet() {
(2)
initControllerAdviceCache();
(3)
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
1.1 設置HttpMessageConverter
HttpMessageConverter
負責將請求信息轉換爲一個對象(類型爲 T),將對象(類型爲 T)輸出爲響應信息
1.2 initControllerAdviceCache
這一步目的是對 @ControllerAdvice
標註的Bean的處理
這裏簡單介紹下@ControllerAdvice
註解意義,通過@ControllerAdvice
標註的類一般作爲全局異常處理,全局數據綁定,全局數據預處理等。
例如:我們在一個Controller裏拋出一個自定義異常時,通常都會有一個@ControllerAdvice
定義的類中處理此異常。
也就是說:@ControllerAdvice
標註類 後,他已經成爲一種處理工具。自然的SpringMVC在初始化時會把這些 開發人員定義的處理工具找到 緩存起來。
源碼中initControllerAdviceCache()
的邏輯是
- 找到該Advice Bean內所有的標註有
@ModelAttribute
但沒標註@RequestMapping
的緩存到一起 - 找到該Advice Bean內所有的標註有
@InitBinder
的方法緩存在一起 - 實現了接口
RequestBodyAdvice/ResponseBodyAdvice
們,緩存到requestResponseBodyAdvice
集合的前面
1.3 設置默認參數解析工具
在getDefaultArgumentResolvers()
方法中設置了大量的SpringMVC已經準備好的參數解析工具。爲handlerApater
處理做準備。
列如:我們常用@PathVariable註解。其實就是在此處設置的PathVariableMethodArgumentResolver
解析工具幫我們解析的。
@PostMapping("/post/{id}/{name}")
public void Post(@PathVariable(value = "id") String id,@PathVariable(value = "name") String name, HttpServletRequest request, HttpServletResponse response){
}
工具設置邏輯爲:
- 先設置針對註解的參數解析工具
- 基於type類型的解析工具設置
- 設置用戶定義的參數解析工具
- 最終解析工具,如果上邊都沒解析,此處設置解析工具最終處理
這些參數解析工具統一交給HandlerMethodArgumentResolverComposite
。從他的名字也可以看出解析工具混合器
,說白了就是解析工具集合
這裏提一下自己的一點小理解: 我在很多框架中都見過類型
xxxComposite
混合器,本質上就是同一個東西的集合。
既然有了集合爲啥還要多一層出來呢?
個人覺得混合器就好比工頭,當我們跟工人打交道時,直接與工頭交涉,比跟多個工人交涉方便的多。
總結爲:分工明確,高內聚低耦合
1.4 設置返回值處理工具
提供對HandlerMethod返回值的支持,比如@ResponseBody
等同參數解析器邏輯差不多,
最終初始化HandlerMethodReturnValueHandlerComposite
混合器,承載返回值處理器集合
1.5 其他
除了上述初始化外,當然還有其他細節,感興趣可以去閱讀源碼
2.handle處理
一切準備就緒後,下面就是調用了
再回頭看下DispatcherServlet
中,HandlerAdapter
的調用起點
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
獲取當前hangdler的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
走過攔截器的前置處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
通過適配器執行調用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
過攔截器後置處理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
RequestMappingHandlerAdapter
handle方法在父類中AbstractHandlerMethodAdapter
回調RequestMappingHandlerAdapter.handleInternal
方法
2.1 handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
...
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
這裏只要是
- request請求方法的檢查,
- 同一個Session下是否要串行(同步)
- 調用
invokeHandlerMethod
- 處理Cache-Control這個請求頭
2.1 invokeHandlerMethod
執行handler方法,好戲正式登場
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//對handlerMethod進行封裝。
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//設置參數處理器
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
//設置返回值處理器
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//調用
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
由於invokeHandlerMethod
方法旁系邏輯比較多。我們抓住主線來講
首先 會將handlerMethod
封裝成ServletInvocableHandlerMethod
那麼,爲啥要做此封裝呢?
從其繼承關係說起:
- 類
handlerMethod
: 是我們定義的接口方法的變體,只是數據的承載,不具有執行能力 - 類
InvocableHandlerMethod
: 賦予HandlerMethod可被執行能力。既然執行就涉及到參數的處理。其實吧,InvocableHandlerMethod
就是爲了參數的解析 - 類
ServletInvocableHandlerMethod
: 它是對InvocableHandlerMethod的擴展,它增加了返回值和響應狀態碼的處理 。本質就是爲了返回值的處理
分工明確,很舒服。
調用ServletInvocableHandlerMethod.invokeAndHandle方法
開啓兩大工作
invocableMethod.invokeAndHandle(webRequest, mavContainer);
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//調用目標方法。
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
//返回值的處理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
2.3 處理參數執行目標方法
上面說到參數的處理是放到InvocableHandlerMethod
完成的,invokeForRequest
方法邏輯就在InvocableHandlerMethod
invokeForRequest(webRequest, mavContainer, providedArgs);
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//解析參數
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
//反射調用目標方法
Object returnValue = doInvoke(args);
//目標方法返回值返回
return returnValue;
}
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
1
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
2
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
3
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
這裏主要有3個點:
(1. 反射獲取目標方法上的參數集合
(2. 遍歷每個參數信息
(3. 對每個參數信息,都使用argumentResolvers
參數處理混合器查找一遍是否有符合當前參數的參數處理器,有就用找到的參數處理器,從request
中把參數值解析出來;沒有就拋出異常
參數值解析完成後,就可以調用目標方法。獲得返回值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
執行目標方法,到這裏纔算是真正的進入咱們開發人員寫Controller裏面了。
Object returnValue = doInvoke(args);
2.4 返回值處理
在上面調用完成目標方法後,獲得了返回值,回到ServletInvocableHandlerMethod
進行返回值的處理
this.returnValueHandlers.handleReturnValue(
returnValue,
getReturnValueType(returnValue),
mavContainer, webRequest
);
handleReturnValue方法
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
返回值處理邏輯: 從返回值處理混合器中,找到支持目標方法返回的處理器,進行處理。
需要注意的是:
當我們使用@RequestBody
註解時,RequestResponseBodyMethodProcessor.handleReturnValue
處理返回值,會將返回值寫入到輸出流時,此時請求已經有了返回。
但是:HadnlerAdapter
的邏輯是沒有走完的。
再回到RequestMappingHandlerAdapter.invokeHandlerMethod
方法的後半部分
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
我們看到他依然會調用getModelAndView方法做一個結果統一性處理,返回一個ModelAndView
對象。
在DispatcherServlet
裏會根據ModelAndView
對象的值進行處理。爲null不渲染頁面,不爲空渲染頁面。
至此:HandlerApater的執行原理大輪廓基本就是這樣了。更多細節可以去閱讀源碼去體會
總結:
想想寫個接口時,參數接收時如此方便,看完RequestMappingHandlerAdapter
後,不得不不感謝RequestMappingHandlerAdapter背後所做的工作
最後再次總結一下HandlerApater的主要工作:request 與 handler之間的參數映射,返回值處理,適配雙方的不兼容問題
再次對Spring 框架體系膜拜,一個優秀的輪子,應該是分工明確,可擴展性好。
Spring 我願稱你爲最強
如果本文任何錯誤,請批評指教,不勝感激 !
如果文章哪些點不懂,可以聯繫我交流學習!
微信公衆號:源碼行動
享學源碼,行動起來,來源碼行動