@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的核心,它對請求進行排程,收到請求後會進入DispatcherServlet
的doDispatch
方法中:
getHandler
方法獲取請求對應的Handler處理器;handler
獲取對應的介面卡,這裡用到了介面卡模式;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;
}
所以回到RequestMappingHandlerAdapter
的handleInternal
方法,裡面呼叫了invokeHandlerMethod
方法進行處理:
ServletInvocableHandlerMethod
;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();
}
}
}
ServletInvocableHandlerMethod
的invokeAndHandle
中呼叫了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
方法獲取請求中的引數,處理邏輯如下:
呼叫getMethodParameters
獲取方法中的引數,也就是我們的請求處理器方法中的所有引數,上面看到submit只接收了一個UserInfo型別的引數,這裡可以從斷點中看到parameters中只有一個元素,型別為UserInfo:
對獲取到方法中的所有引數進行遍歷,通過處理器呼叫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
中實現:
呼叫getArgumentResolver
方法獲取對應的引數處理器resolver
;
呼叫resolver
的resolveArgument
方法進行引數解析;
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);
}
}
從斷點中可以看到此時的resolver
是RequestResponseBodyMethodProcessor
型別的:
進入到RequestResponseBodyMethodProcessor
的resolveArgument
方法中,它又呼叫了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
方法處理邏輯如下:
遍歷所有HTTP訊息轉換器,判斷是否支援解析當前的請求引數型別;
如果轉換器支援解析當前的引數型別並且有訊息體內容,呼叫轉換器的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
型別會進入到MappingJackson2HttpMessageConverter
,read
方法在其父類別AbstractJackson2HttpMessageConverter
,處理邏輯如下:
獲取引數的Class型別,從斷點中可以看出是[class com.example.demo.model.UserInfo];
呼叫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