【Spring】@RequestBody的實現原理

2023-07-22 06:00:37

@RequestBody註解可以用於POST請求接收請求體中的引數,使用方式如下:

@Controller
public class IndexController {
    
    @PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
    public void submit(@RequestBody UserInfo userInfo) {
        System.out.println(userInfo.toString());
    }
}

那麼是如何從請求中解析資料設定到對應的引數中呢,接下來就從原始碼的角度一探究竟。

DispatcherServlet是Spring MVC的核心,它對請求進行排程,收到請求後會進入DispatcherServletdoDispatch方法中:

  1. 呼叫getHandler方法獲取請求對應的Handler處理器;
  2. 根據handler獲取對應的介面卡,這裡用到了介面卡模式;
  3. 呼叫介面卡的handle方法處理請求,它會返回一個ModelAndView物件;
public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				// 檢查是否有Multipart
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 根據請求獲取對應的處理器
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 根據handler獲取對應的介面卡
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// ...

				// 處理請求
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// ...
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		// ...
	}
}

通過POSTMAN模擬請求,在程式碼中打斷點可以看到HandlerAdapter的型別為對RequestMappingHandlerAdapter:

handle方法在其父類別AbstractHandlerMethodAdapter中實現,在它的handle方法中,又呼叫了handleInternal方法處理請求,handleInternal是一個抽象方法,由具體的子類實現:

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
	    // 處理請求
		return handleInternal(request, response, (HandlerMethod) handler);
	}

	@Nullable
	protected abstract ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
}

所以回到RequestMappingHandlerAdapterhandleInternal方法,裡面呼叫了invokeHandlerMethod方法進行處理:

  1. 建立ServletInvocableHandlerMethod
  2. 呼叫invokeAndHandle方法繼續請求處理;
  3. 呼叫getModelAndView方法返回ModelAndView;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    @Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					// 執行請求
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// 執行請求
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// 執行請求
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
		// ...
		return mav;
	}

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

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			// ...
            // 建立ServletInvocableHandlerMethod
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			// 呼叫invokeAndHandle方法處理請求
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}
			// 返回ModelAndView
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
}

ServletInvocableHandlerMethodinvokeAndHandle中呼叫了invokeForRequest方法執行請求,它的實現在其父類別InvocableHandlerMethod中:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    	// 執行請求
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		// ...
	}
}

invokeForRequest中又呼叫了getMethodArgumentValues方法獲取請求中的引數,處理邏輯如下:

  1. 呼叫getMethodParameters獲取方法中的引數,也就是我們的請求處理器方法中的所有引數,上面看到submit只接收了一個UserInfo型別的引數,這裡可以從斷點中看到parameters中只有一個元素,型別為UserInfo:

  2. 對獲取到方法中的所有引數進行遍歷,通過處理器呼叫resolveArgument方法解析請求中的資料,解析每一個引數對應的值;

public class InvocableHandlerMethod extends HandlerMethod {
	@Nullable
	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);
			// ...
			try {
				// 呼叫resolveArgument從請求中解析對應的資料
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			// ...
		}
		return args;
	}
}

resolveArgument方法在HandlerMethodArgumentResolverComposite中實現:

  1. 呼叫getArgumentResolver方法獲取對應的引數處理器resolver

  2. 呼叫resolverresolveArgument方法進行引數解析;

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        // 獲取對應的引數處理器
		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		// 解析引數
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}
}

從斷點中可以看到此時的resolverRequestResponseBodyMethodProcessor型別的:

進入到RequestResponseBodyMethodProcessorresolveArgument方法中,它又呼叫了readWithMessageConverters方法解析引數,最終會進入到
AbstractMessageConverterMethodArgumentResolve中的readWithMessageConverters方法:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    @Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		// 通過轉換器進行引數解析
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);
		// ...
		return adaptArgumentIfNecessary(arg, parameter);
	}

	@Override
	protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
		// 呼叫AbstractMessageConverterMethodArgumentResolver中readWithMessageConverters方法讀取引數
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		if (arg == null && checkRequired(parameter)) {
			throw new HttpMessageNotReadableException("Required request body is missing: " +
					parameter.getExecutable().toGenericString(), inputMessage);
		}
		return arg;
	}
}

readWithMessageConverters方法處理邏輯如下:

  1. 遍歷所有HTTP訊息轉換器,判斷是否支援解析當前的請求引數型別;

  2. 如果轉換器支援解析當前的引數型別並且有訊息體內容,呼叫轉換器的read方法進行解析;

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Nullable
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		// ...
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
			// 遍歷所有的訊息轉換器
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				// 判斷是否支援當前引數型別的讀取
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					// 如果有訊息體
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						// 呼叫read方法進行讀取
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}

		// ...
		return body;
	}
}

這裡列舉一些訊息轉換器的型別:

對於application/json;charset=UTF-8型別會進入到MappingJackson2HttpMessageConverterread方法在其父類別AbstractJackson2HttpMessageConverter,處理邏輯如下:

  1. 獲取引數的Class型別,從斷點中可以看出是[class com.example.demo.model.UserInfo];

  2. 呼叫readJavaType方法解析引數
    (1)獲取ContentType,前面可以看到請求接收的型別為application/json;
    (2)獲取字元集,這裡的字元集為UTF-8;
    (3)建立ObjectMapper物件,並從請求體中讀取JSON資料,轉為JAVA物件;

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {

    @Override
	public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {
		// 獲取引數的Class型別
		JavaType javaType = getJavaType(type, contextClass);
		// 解析引數
		return readJavaType(javaType, inputMessage);
	}

	private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
		// 獲取ContentType
		MediaType contentType = inputMessage.getHeaders().getContentType();
		// 獲取字元集
		Charset charset = getCharset(contentType);
		ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
		Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);
		boolean isUnicode = ENCODINGS.containsKey(charset.name());
		try {
		    // ...
			if (isUnicode) {
				// 獲取HTTP請求體中的JSON資料,轉為JAVA物件
				return objectMapper.readValue(inputMessage.getBody(), javaType);
			}
			else {
				Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
				return objectMapper.readValue(reader, javaType);
			}
		}
		// ....
	}
}

到這裡已經成功從HTTP請求體中的JSON資料,並轉為JAVA物件,完成了引數的設定。

Spring版本:5.3.4