享讀SpringMVC源碼4-感謝RequestMappingHandlerAdapter

今天很難,明天更難,後天也許更難

回顧

上期說道,HandlerAdapter存在的意義,就是充當request,reponse與我們定義的各種形式handler之間的參數適配,返回值適配。

當我們在享受着SpringMVC帶來的多樣化參數接收形式,以及簡便的返回值操作時,殊不知,HandlerAdapter在背後默默的爲我們奉獻着。

RequestMappingHandlerAdapter

適用於@RequestMapping註解標註的Handler,是處理我們定義的controller接口最重要的HandlerAdapter。基於他的重要地位,本文講講他是如何工作的。

1.初始化

先從其初始化開始下手

爲了應對handler的各種樣式的參數接收,在handlerApater裏,就需要大量的轉換工具來類處理這些東西。

所以:初始化的過程中,就是要設置各種參數工具,返回值處理工具等等,這些工具可以是Spring 提供的或者開發人員自己定義的。

1public 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() {2initControllerAdviceCache();3if (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);
}

RequestMappingHandlerAdapterhandle方法在父類中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 我願稱你爲最強


如果本文任何錯誤,請批評指教,不勝感激 !
如果文章哪些點不懂,可以聯繫我交流學習!

微信公衆號:源碼行動
享學源碼,行動起來,來源碼行動

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章