学习@RequestBody注解解析请求参数流程
⼀、背景
研究对象是Springboot的⼀个后台Web系统。
想了解,在SpringMVC对@RequestBody的参数进⾏注⼊之前,执⾏了InputStream()/Reader()或者
⼆、Controller中Handler的注册
留意到每次系统启动的时候,Spring会打印这类⽇志
Mapped "{[/xxx/yyyy/aaa],methods=[POST]}" onto ......
因此,对于Controller中对RequestMapping的解析,就从此处的⽇志开始。看看在解析RequestMapping的时候,有没有对
springmvc的注解有哪些
@RequestBody注解的参数进⾏处理。
1. 到打印这⾏⽇志的类以及⾏数, RequestMappingHandlerMapping:547.
发现这个类中总共都没有547⾏,那么就去它继承的⽗类中去,结果在AbstractHandlerMethodMapping这个类中到了对应的⾏和⽅法。
public void register(T mapping, Object handler, Method method) {
// 省略
}
从⽅法名以及参数上来看,肯定是将Controller中每个RequestMapping对应的Method注册起对应关系。但是⼊参到底是啥也不清楚,那么就看哪些地⽅调⽤了这个⽅法。
发现了如下的调⽤链路:
AbstractHandlerMethodMapping#initHandlerMethods
AbstractHandlerMethodMapping#detectHandlerMethods
AbstractHandlerMethodMapping#registerHandlerMethod
AbstractHandlerMethodMapping#register
2. 那么从initHandlerMethods开始看
protected void initHandlerMethods() {
// 省略,获取所有beanNames
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
// 关键代码处
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
在这个⽅法中的关键代码处可以看到,会对bean进⾏判断 isHandler , 如果是Handler,那么就去解析⾥⾯的Methods
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
可以看到,只要是有@Controller或者@RequestMapping的Bean,都是Handler。
也可以看到所谓的Handler就是我们⽇常说的⾝为Controller的Bean。
3. 继续进⼊detectHandlerMethods⽅法中
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : Class());
if (handlerType != null) {
final Class<?> userType = UserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType); //关键处1
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping); //关键处2
});
}
这个⽅法就是获取Controller中的所有⽅法,并⼀个个去解析
在关键处1,⾥⾯所做的事情就是先判断这个⽅法是否有@RequestMapping注解,其次获取注解⾥⾯的信息(请求头、请求⽅法,请求参数等)并记录到RequestMappingInfo对象中。并且假如Controller上也有RequestMapping注解,那就还要进⾏⼀些合并操作。都做完了就返回⼀个整体的RequestMappingInfo对象
在关键处2,就是建⽴Mapping与Method的映射关系,等到实际调⽤的时候,根据请求的地址解析得到Mapping,取出相应的Method进⾏调⽤。
当然这⾥⾯是有代理的,具体细节就没有去详细看。因为我的⽬的是先了解流程。
在这个解析过程中,好像并没有对@RequestBody的处理,那么就看看在实际调⽤的时候,是怎么处理@RequestBody的
⼆、@RequestBody参数解析
⼀个Http请求,必然从Servlet的doService开始的(抛开与过滤器),那么就从SpringMVC的DispatcherServlet开始⼊⼿。顺着doService看到doDispatch,然后doDispatch中可以看到⼀⾏:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, Handler());
那么实际的接⼝处理流程就应该是在这⾥⾯了,跟进可以看到RequestMappingHandlerAdapter#handleInternal⽅法中,
mav = invokeHandlerMethod(request, response, handlerMethod);
从⽅法名就可以看出是进⾏Controller中的Method调⽤的,继续跟进去,会发现RequestMappingHandlerAdapter有⼀个成员变量是argumentResolvers,那么从名称来看,很⼤概率就是我想要到的参数解析器。
这个argumentResolvers是个HandlerMethodArgumentResolverComposite类的实例,进⼊这个类中,就有⼀个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("Unknown parameter type [" + ParameterType().getName() + "]");
}
solveArgument(parameter, mavContainer, webRequest, binderFactory);
}
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = (parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (logger.isTraceEnabled()) {
}
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
先不去追究细节,这两个⽅法所表达出来的意思很明了。先根据Controller中的Method中的参数,来获取到对应的解析器,也就是HandlerMethodArgumentResolver的⼀个实例。然后⽤这个解析器来解析参数。
其中,获取解析器的时候,会先从缓存中获取,如果缓存中没有,那么就遍历所有的解析器,到⼀款能够解析这个参数的解析器,并存⼊缓存中。
对于@RequestBody的解析器,可以先看看HandlerMethodArgumentResolver有哪些实现类,发现有很多种解析器,包括我们常⽤的⼀些@RequestParam,@RequestHeader等等
其中想要去看到就是框红的那个。进⼊这个类中,到resolveArgument⽅法:
/**
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = stedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, NestedGenericPar
ameterType());
String name = VariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = ateBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (BindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, BindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, BindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
这个⽅法也是有两步,先readWithMessageConverters解析参数,后⾯就是对这个参数进⾏校验,⽐如你使⽤了require=true,或者
@Valid/@Validate。
接着readWithMessageConverters往⾥⾛,来到了AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters中,其中对于我想要了解的东西,最关键的⼀⾏代码就是
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
在这个构造函数⾥⾯可以看到
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
this.headers = Headers();
InputStream inputStream = Body();
if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (ad() != -1 ? inputStream : null);
}
else {
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = ad();
if (b == -1) {
this.body = null;
}
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
这⾥⾯就对输⼊流进⾏了包装处理,流程图:
其中PushbackInputStream,就是增加了⼀个unread功能。read是往前读⼀个字节,⽽unread就是往后读⼀个⾃⼰。
三、结论
1. Parameter()不会对@RequestBody的解析造成影响,因为这完全是两种获取参数的⽅式,两个赛道。对于POST请求⽽⾔,getParameter是解
析application/x-www-form-urlencoded类型的参数,⽽@RequestBody是解析application/json类型的参数
2. ⼀般情况下,假如你在过滤器或任何@RequestBody解析之前的地⽅,读完了请求流,那么@RequestBody是获取不到参数内容的。
3. 因此对于需要可重复读的请求流,⼀般⽹上也给了⽅案,对Request进⾏⼀层包装,且要覆写其中的getInputStream⽅法,这样才能随便通过
getInputStream来读请求流。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论