SpringMVC請求引數解析

是馮吉榮呀發表於2021-04-29

請求引數解析

客戶端請求在handlerMapping中找到對應handler後,將會繼續執行DispatchServletdoPatch()方法。

首先是找到handler對應的介面卡。

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

進入到getHandlerAdapter(mappedHandler.getHandler())方法中

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

這裡存在多個介面卡,如圖:

其中使用@RequestMaping註解修飾的控制器都將適配第一個介面卡;而函式式方法將會使用第二個介面卡。

跟蹤請求,這裡將會獲得第一個介面卡,判斷也簡單,如下:

public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

如果是HandlerMethod型別的處理器就採用這個介面卡,而客戶端請求正好對應的是HandlerMethod處理器。

找到介面卡後,將會真正執行處理器邏輯。如下:

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

進入RequestMappingHandlerAdapter,執行介面卡核心方法:

@Override
	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) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		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;
	}

其核心程式碼為實際執行處理器方法:

mav = invokeHandlerMethod(request, response, handlerMethod);

同樣,我們開啟RequestMappingHandlerAdapter中的invokeHandlerMethod方法:

@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
            //通過處理器獲得真正的執行方法及其引數列表
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            //給執行方法物件新增引數解析器
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
            //給執行方法物件新增返回值處理器
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
            //處理器物件裝配完成,執行控制器方法
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}
            

在這個關鍵方法中,首先執行請求對應的控制器邏輯,之後進行系列處理,根據返回值處理器處理返回值。

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()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
            //處理返回值
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

以下給出部分引數解析器及返回值處理器截圖:

引數解析器。對應每一個引數(路徑變數、矩陣變數、獲得請求頭、請求域等)的獲取方式

返回值處理器。ModelAndView、ResponseBody等。每個處理器處理不同類別的返回值型別。

接下來,真正進入到最終執行method方法invocableMethod.invokeAndHandle(webRequest, mavContainer);,這裡是真是執行控制器中對映的方法。

以下為獲得引數列表對應值的邏輯,引數獲取完成後將會執行真正的控制器邏輯。

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    //通過引數解析器獲取引數列表每一個引數的值
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

進入解析邏輯,解析是對比每一個引數繫結的註解,如果註解一致將會使用對應的解析器將請求傳遞的引數值獲取到。

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        //獲得控制器引數列表,每一個引數包含其引數型別,引數次序、註解修飾(用於使用對應解析器)
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
            //輪循獲得引數
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
            //判斷解析器是否支援當前引數的解析
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
                //獲得引數值的核心方法
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

檢視解析器是否指出當前引數部分程式碼,可以瞭解到SpringMVC的快取策略。

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    //從快取中獲取當前引數的解析器
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	//快取中不存在則將這個引數對應的解析器加到快取中,提升後續相同請求響應速度。	
    if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

引數列表

以下為獲得的引數列表第一個引數部分屬性

其對應的是控制器中id引數:

@GetMapping(value = "/student01/{id}/car/{name}")
    public Map<String, Object> testAnnotation(@PathVariable(name = "id") String id){
        Map<String, Object> map = new HashMap<>();
        map.put("id", id);
    return map;
    }

以上即為獲得請求Handler對應的介面卡,處理引數對映、執行控制器邏輯、返回值處理的核心原始碼處理。

相關文章