Spring 是如何将前端请求中的参数解析到指定对象的
Posted HelloWorld_EE
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 是如何将前端请求中的参数解析到指定对象的相关的知识,希望对你有一定的参考价值。
Spring 是如何将前端请求中的参数解析到指定对象的
注:文章稍微偏源码,对照着源码debug看效果更好一点。
先说下背景,最近看一个项目,有一个接口的定义如下
@PostMapping(value = "/conditionConfig")
public BaseResponse<PageInfo<ConfigDTO>> conditionConfig(@Valid @RequestBody ConfigQuery configQuery)
return configConsumer.conditionConfig(configQuery);
其中ConfigQuery模型的定义如下:
@Data
public class ConfigQuery implements Serializable
//其他参数省略
@ApiModelProperty("起始时间")
private Date startTime;
@ApiModelProperty("截止时间")
private Date endTime;
而关于startTime和endTime的请求参数,我以为和正常情况一样,会传类似于2020-12-05T11:29:21.854Z 的时间格式过来。具体如下(注:该格式的请求数据也是可以正常被解析而正常工作的)
curl 'localhost:8080/conditionConfig' \\
-H 'Connection: keep-alive' \\
-H 'Pragma: no-cache' \\
-H 'Cache-Control: no-cache' \\
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/86.0.4240.183 Safari/537.36' \\
-H 'Content-Type: application/json' \\
-H 'Accept: */*' \\
-H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8' \\
--data-binary '"endTime": "2020-12-05T11:29:21.854Z","startTime": "2020-12-05T11:29:21.854Z"' \\
--compressed \\
--insecure
而看了下前端的请求,发现前端传递的是Long类型的时间戳,具体如下
curl 'localhost:8080/conditionConfig' \\
-H 'Connection: keep-alive' \\
-H 'Pragma: no-cache' \\
-H 'Cache-Control: no-cache' \\
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36' \\
-H 'Content-Type: application/json' \\
-H 'Accept: */*' \\
-H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8' \\
--data-binary '"startTime":1606579200000,"endTime":1607270400000' \\
--compressed \\
--insecure
通过Debug观察,发现后端接受到该请求,是可以自动将Long类型的时间戳转换为Date类型。这个时候我的好奇心就产生了,哎哟,这个自动转换是如何来实现的?
于是就带着这个疑问开始了在Spring的源码中寻找答案之旅,最终也就有了此篇文章的产生。
在这篇文章中分析了SpringMVC中doDispatch方法中的大致实现思路
https://blog.csdn.net/u010412719/article/details/79068994
但分析的比较粗糙,并没有对参数解析这部分进行分析。本篇文章将带着如上的困惑来分析Spring是如何来解析前端传过来的参数的。
如下是doDispatch方法中开始处理reuqest的一行代码:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这行代码的ha是RequestMappingHandlerAdapter对象实例,其handle方法直接调用了如下的handleInternal方法,我们关注handleInternal方法如下的那行代码即可。
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
this.checkRequest(request);
ModelAndView mav;
if(this.synchronizeOnSession)
//省略
else
//重点
mav = this.invokeHandlerMethod(request, response, handlerMethod);
//...
return mav;
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
ServletWebRequest webRequest = new ServletWebRequest(request, response);
Object result;
try
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
//注意这里:就是将在RequestMappingHandlerAdapter初始化的一系列参数解析期给到ServletInvocableHandlerMethod,在该类的内部会使用到。
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())
result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if(this.logger.isDebugEnabled())
this.logger.debug("Found concurrent result value [" + result + "]");
invocableMethod = invocableMethod.wrapConcurrentResult(result);
//真正的执行逻辑
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
if(!asyncManager.isConcurrentHandlingStarted())
ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
return var15;
result = null;
finally
webRequest.requestCompleted();
return (ModelAndView)result;
invokeHandlerMethod 方法中关注两点:
1)new了一个ServletInvocableHandlerMethod对象invocableMethod。
2)调用invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
因此,继续看invocableMethod.invokeAndHandle的内部实现。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception
//1.执行请求,得到返回值
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);
if(returnValue == null)
if(this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled())
mavContainer.setRequestHandled(true);
return;
else if(StringUtils.hasText(this.getResponseStatusReason()))
mavContainer.setRequestHandled(true);
return;
mavContainer.setRequestHandled(false);
try
//2.对返回值进行处理 this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
catch (Exception var6)
if(this.logger.isTraceEnabled())
this.logger.trace(this.getReturnValueHandlingErrorMessage("Error handling return value", returnValue), var6);
throw var6;
invokeAndHandle主要干了两件事情:
1)执行请求,得到返回值
2)对返回值进行处理
由于我们重点关注的是第一部分,因此我们继续看invokeForRequest方法。
InvocableHandlerMethod
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception
//1.解析参数,例如:@RequestBody、@RequestParam注解的参数,
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if(this.logger.isTraceEnabled())
this.logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "' with arguments " + Arrays.toString(args));
//2.真正开始执行
Object returnValue = this.doInvoke(args);
if(this.logger.isTraceEnabled())
this.logger.trace("Method [" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "] returned [" + returnValue + "]");
return returnValue;
上面这个方法主要也干了两件事情
1)解析参数,例如:如何将请求中的参数映射到使用@RequestBody、@RequestParam注解的参数上去
2)真正开始执行业务逻辑
在文章开始的地方,我们就说明了我们的目的:Spring是如何来解析请求的参数的,到这里,我们就离目标越来越近了,因此我们主要看第一部分,即getMethodArgumentValues方法的内部实现。
getMethodArgumentValues方法的源码如下:
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception
//1.得到目标参数信息,即我们接口中定义的参数信息。
MethodParameter[] parameters = this.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] = this.resolveProvidedArgument(parameter, providedArgs);
if(args[i] == null)
//2.1判断此参数是否能被解析
if(this.argumentResolvers.supportsParameter(parameter))
try
//2.2.使用HandlerMethodArgumentResolverComposite 进行解析
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
catch (Exception var9)
if(this.logger.isDebugEnabled())
this.logger.debug(this.getArgumentResolutionErrorMessage("Failed to resolve", i), var9);
throw var9;
else if(args[i] == null)
throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() + ": " + this.getArgumentResolutionErrorMessage("No suitable resolver for", i));
return args;
上面对主要的代码添加了响应的注释。
下面来看HandlerMethodArgumentResolverComposite中的resolveArgument方法是如何来解析参数的。
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
if(resolver == null)
throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
else
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
HandlerMethodArgumentResolverComposite 该类中定义了一系列的解析器,来解析参数,这就是比较明显的策略模式,方便扩展。
例如:
1)@RequestBody 注解的参数使用RequestResponseBodyMethodProcessor
2)@RequestParam 注解的参数使用 RequestParamMethodArgumentResolver
而定义的这一系列的参数解析器,是在那里被初始化的呢?
答案:是在RequestMappingHandlerAdapter的afterPropertiesSet方法中,具体如下
public void afterPropertiesSet()
this.initControllerAdviceCache();
List handlers;
if(this.argumentResolvers == null)
//这里,将一系列的解析器加入到了HandlerMethodArgumentResolverComposite类型的对象argumentResolvers中
handlers = this.getDefaultArgumentResolvers();
this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
if(this.initBinderArgumentResolvers == null)
handlers = this.getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers);
if(this.returnValueHandlers == null)
handlers = this.getDefaultReturnValueHandlers();
this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers);
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers()
List<HandlerMethodArgumentResolver> resolvers = new ArrayList();
resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
if(this.getCustomArgumentResolvers() != null)
resolvers.addAll(this.getCustomArgumentResolvers());
resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
继续看,回到文章的最开头,我们的接口参数是使用@RequestBody 注解
@PostMapping(value = "/conditionConfig")
public BaseResponse<PageInfo<ConfigDTO>> conditionConfig(@Valid @RequestBody ConfigQuery configQuery)
return configConsumer.conditionConfig(configQuery);
因此,参数的解析工作就是由RequestResponseBodyMethodProcessor来完成,来看下该类的resolveArgument方法
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
parameter = parameter.nestedIfOptional();
//重点:解析参数
Object arg = this.readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if(arg != null)
this.validateIfApplicable(binder, parameter);
if(binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter))
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return this.adaptArgumentIfNecessary(arg, parameter);
继续看readWithMessageConverters 方法
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException
HttpServletRequest servletRequest = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//重点,解析参数
Object arg = this.readWithMessageConverters(inputMessage, parameter, paramType);
if(arg == null && this.checkRequired(parameter))
throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getMethod().toGenericString());
else
return arg;
AbstractMessageConverterMethodArgumentResolver
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException
boolean noContentType = false;
MediaType contentType;
try
contentType = inputMessage.getHeaders().getContentType();
catch (InvalidMediaTypeException var14)
throw new HttpMediaTypeNotSupportedException(var14.getMessage());
if(contentType == null)
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
Class<?> contextClass = parameter != null?parameter.getContainingClass():null;
Class<T> targetClass = targetType instanceof Class?(Class)targetType:null;
if(targetClass == null)
ResolvableType resolvableType = parameter != null?ResolvableType.forMethodParameter(parameter):ResolvableType.forType(targetType);
targetClass = resolvableType.resolve();
HttpMethod httpMethod = ((HttpRequest)inputMessage).getMethod();
Object body = NO_VALUE;
Object inputMessage;
try
inputMessage = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage);
/*
重点:有一系列的message converter
*/
Iterator var10 = this.messageConverters.iterator();
while(var10.hasNext())
HttpMessageConverter<?> converter = (HttpMessageConverter)var10.next();
Class<HttpMessageConverter<?>> converterType = converter.getClass();
if(converter instanceof GenericHttpMessageConverter)
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter)converter;
//看此converter是否能解析该类型的参数数据,如果能,则进行解析
//例如:MappingJackson2HttpMessageConvertor 就能解析二进制流到我们定义的对象。
if(genericConverter.canRead(targetType, contextClass, contentType))
if(this.logger.isDebugEnabled())
this.logger.debug("Read [" + targetType + "] as \\"" + contentType + "\\" with [" + converter + "]");
if(((HttpInputMessage)inputMessage).getBody() != null)
inputMessage = this.getAdvice().beforeBodyRead((HttpInputMessage)inputMessage, parameter, targetType, converterType);
//真正利用MappingJackson2HttpMessageConvertor此converter来进行解析。
body = genericConverter.read(targetType, contextClass, (HttpInputMessage)inputMessage);
body = this.getAdvice().afterBodyRead(body, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
else
body = this.getAdvice().handleEmptyBody((Object)null, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
break;
else if(targetClass != null && converter.canRead(targetClass, contentType))
if(this.logger.isDebugEnabled())
this.logger.debug("Read [" + targetType + "] as \\"" + contentType + "\\" with [" + converter + "]");
if(((HttpInputMessage)inputMessage).getBody() != null)
inputMessage = this.getAdvice().beforeBodyRead((HttpInputMessage)inputMessage, parameter, targetType, converterType);
body = converter.read(targetClass, (HttpInputMessage)inputMessage);
body = this.getAdvice().afterBodyRead(body, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
else
body = this.getAdvice().handleEmptyBody((Object)null, (HttpInputMessage)inputMessage, parameter, targetType, converterType);
break;
catch (IOException var15)
throw new HttpMessageNotReadableException("Could not read document: " + var15.getMessage(), var15);
if(body != NO_VALUE)
return body;
else if(httpMethod != null && SUPPORTED_METHODS.contains(httpMethod) && (!noContentType || ((HttpInputMessage)inputMessage).getBody() != null))
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
else
return null;
readWithMessageConverters方法中逻辑如下:
1)遍历每个MessageConverter,看是否能对此类型的参数进行解析
2)如果能,则就交由此MessageConverter来解析。
AbstractMessageConverterMethodArgumentResolver解析器中包括一系列的MessageConverter,其中就包括:MappingJackson2HttpMessageConvertor,这个convertor就是用来将前端传过来的Json字符串转换为对象的。而MappingJackson2HttpMessageConvertor 内部是借助于Jackson来完成json到对象之间的转换。
也就是说:Jackson发现json中的startTime是Long类型,而对象是中Date类型,因此就将Long类型的时间戳转换为了Date对象。
到这里,我们就将其实现思路给了解清楚了,希望对你有收获。
最后,真的好就好久没有写文章了。
以上是关于Spring 是如何将前端请求中的参数解析到指定对象的的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 前端请求参数自动映射到枚举,后端响应JSON数据自动解析枚举
如何指定一个请求参数绑定到某个控制器方法参数而不是Spring MVC中的模型属性?