Spring源码解读(第九弹)-请求参数封装,返回值处理

(友情提示: 代码及注释内容比较多,为了不影响体验,建议大屏观看哦)

参数是如何绑定到接口方法上的,接口的返回值又是怎么处理的,在这里你都会有答案!


通过上一篇《Spring源码解读(第八弹)-请求的处理流程,你的接口在调用前后都发生了什么》的内容,想必各位道友对请求处理的大致框架已经有了一个大致的了解了。但是框架里面的细节并没有详细的解释,而这篇的主要内容,就是完成上一篇留下的两个重点细节,请求参数处理返回值处理这两个核心点。

(友情提示: 建议先回顾一下上一篇最后那个接口调用Sequence图哦!)

开端

先上重点代码,很荣幸,这俩货都在同一个方法里面,清晰明了。

//ServletInvocableHandlerMethod.java
public final void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs)
throws Exception
{
// 真正调用controller方法,获得结果(请求参数的封装就在这里面哦)
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
try {
//调用默认的和自定义的所有返回值解析器处理返回结果
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}

invokeForRequest(请求参数封装)

这个方法返回了接口调用的返回值,那我们就从这里跟进去,寻找请求参数的封装的逻辑。

// InvocableHandlerMethod.java
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 封装请求参数
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
...
return this.doInvoke(args);
}
protected Object doInvoke(Object... args) throws Exception {
// 这里就是发起对我们Controller的调用,喜欢的道友上吧
return this.getBridgedMethod().invoke(this.getBean(), args);
...
}

可以看到,在第一行,就返回了一个Object数组的args,这个就是封装好了的参数,然后拿着这个参数,发起对我们controller的调用。

好吧,继续跟进getMethodArgumentValues()这个方法。

getMethodArgumentValues

// InvocableHandlerMethod.java
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 拿到我们接口方法的参数信息
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
// 如果没有参数,直接返回空
return EMPTY_ARGS;
} else {
Object[] args = new Object[parameters.length];
// 一个一个封装请求参数
for(int i = 0; i < parameters.length; ++i) {
MethodParameter parameter = parameters[i];
// 设置参数名称解析器
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// providedArgs这个参数外层没有传递,是个空数组
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
// 如果没有解析器能处理当前参数,就抛出异常
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}

try {
// 解析器处理请求参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
}

return args;
}
}

注意看代码中的注释,大体流程比较容易理解,在for循环中,findProvidedArgument()这个方法逻辑比较简单,就不贴代码了,注意看注释。

  1. 从外层进来provideArgs这个参数是没有传递的,在这个方法里只会返回一个null,紧接着,就会进入后面的if判断逻辑。

  2. 首先检查我们的参数解析器,能不能都解析处理这个参数,如果不能就报错

  3. 如果能处理,那就调用我们的解析器,去处理我们的参数。

我们先来看一下第二步,检查是否能否处理参数这步逻辑,这个方法是在一个叫andlerMethodArgumentResolverComposite的类当中,也就是我们的resolvers类型。

this.resolvers.supportsParameter

// HandlerMethodArgumentResolverComposite.java
public boolean supportsParameter(MethodParameter parameter) {
// 实际上就是检查有没有处理这个参数的解析器
return getArgumentResolver(parameter) != null;
}

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;
}

我们来debug一下,看一下有哪些resolvers,你就明白了。

Spring源码解读(第九弹)-请求参数封装,返回值处理

argument_resolvers.png

看到集合中第2个元素,RequestParamMethodArgumentResolver,我想你应该悟了!又是万能的策略模式。贴一下策略接口。

public interface HandlerMethodArgumentResolver {
// 判断是否支持解析当前参数
boolean supportsParameter(MethodParameter parameter);
// 对当前参数解析
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

由于我的测试接口上是@RequestParam注解,所以,我们直接来看对第二个元素执行的resolver.supportsParameter()方法的调用。

supportsParameterr()

// RequestParamMethodArgumentResolver.java
public boolean supportsParameter(MethodParameter parameter) {
// 判断是否有@RequestParam注解
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
// 判断参数类型是否是map类型
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
...
}

逻辑很简单,判断是否包含指定的注解。到这里,就很清晰了,不同的处理参数的方式,都有对应的一个解析器,比如@RequestBody,@PathVariable之类的。

拿到我们的解析器之后,也就说明当前参数能够被处理了。

再次回到这一节的最开始,我们最开始的那个判断就通过了,接下来就是通过解析器去解析这个参数了。

this.resolvers.resolveArgument

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 取resolver,上面已经套理论过这个了
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
// ...
// 调用解析器解析这个参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

可以看到这个的逻辑很简单,再次调用getArgumentResolver()方法拿到解析器,然后调用解析器的方法,解析当前参数。我们还是以RequestParamMethodArgumentResolver为例来探讨。

RequestParamMethodArgumentResolver

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 拿到参数名,也就是注解上的名字,如果没有就默认用方法上的参数名
AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// 处理一下参数是否是java.util.Optional<T>类型的情况
MethodParameter nestedParameter = parameter.nestedIfOptional();

// 处理参数名中有EL表达式的情况(所以参数名也是可以使用${}的哦,没用过吧,哈哈)
Object resolvedName = resolveStringValue(namedValueInfo.name);

// 根据参数名,从请求中获取参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
// 如果没有找到参数值,就判断是否是必须参数,是否有默认值等逻辑,做相应的处理
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
// 如果参数是空字符串,并且有默认值可以取,就取默认值,默认值也是可以用EL表达式的哦
arg = resolveStringValue(namedValueInfo.defaultValue);
}

if (binderFactory != null) {
// 处理有binder的情况,针对@InitBinder注解哦
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 把值绑定到我们的入参上,比如当我们的接口入参是一个对象的时候
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
}
// 处理解析后的结果
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}

方法虽然有点长,但是注释也比较清楚了。无非不过就是拿到参数名,然后根据参数名从请求中取值,再处理解析后的结果,接着就返回了。我们这里讨论的RequestParamMethodArgumentResolver这个类,handleResolvedValue()方法是空实现。

对于请求参数的处理,不同的处理参数的形式,都有一个对应的解析去解析这个参数,这里我们只讨论了最简单的@RequestParam的解析方式,剩下的还有很多的解析器,就留给各位道友了。

至此,请求前的参数封装逻辑就讨论完了,是不是感觉也很简单。

那么趁热打铁,我们接着讨论返回值的处理,这坨的大体逻辑其实和请求参数的差不多,所以你会更容易理解!

handleReturnValue(返回值处理)

返回值的处理都在本篇开端的的这行代码中,忘记了的道友,可以去本篇开端那段核心代码找找记忆哦!

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

这个方法中第二个参数是通过getReturnValueType()方法返回的,这个方法会返回一个MethodParameter类型的结果,其实就是包含当前接口方法的一些信息。刚刚封装请求参数的时候也有用到哦!这个简单的方法就不介绍了,我们直接进入核心方法。

this.returnValueHandlers.handleReturnValue()

// HandlerMethodReturnValueHandlerComposite.java
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest)
throws Exception
{
// 找到能够处理返回值的handler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 处理返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

哈哈哈,看到没有,这次又变成了HandlerMethodReturnValueHandlerComposite这个货,跟请求参数处理那货一样,逻辑也差不多。我们先来看一下获取handler这坨。

selectHandler

private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
// 判断是否是异步请求
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 遍历当前所有的handler,选一个能够处理当前返回值的,然后返回这个handler
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 判断是否能够处理当前返回值
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}

哇靠,熟悉吧大兄dei,又是一样的逻辑。我们直接debug,看下有哪些handler。

Spring源码解读(第九弹)-请求参数封装,返回值处理

return_value_handlers.png

依旧是万能的策略模式,那我们来重点来看一下策略的接口。

public interface HandlerMethodReturnValueHandler {
// 判断当前处理器能否处理当前方法的返回值
boolean supportsReturnType(MethodParameter returnType);
// 处理当前方法的返回值
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception
;
}

跟请求参数封装那坨相比,不能说相似,只能说特喵的一毛一样

RequestResponseBodyMethodProcessor

由于我们常用的都是@ResponseBody注解,这里就拿处理这个注解的RequestResponseBodyMethodProcessor这个类来动刀了。虽然它的名字后缀叫Processor,但是名字不重要,只是个代号,我们看看它的继承结构。

Spring源码解读(第九弹)-请求参数封装,返回值处理

RequestResponseBodyMethodProcessor_structure.png

看到了吧,它确实实现了HandlerMethodReturnValueHandler接口的哦。

哈哈哈,没错,它还实现了我们请求参数封装的那个HandlerMethodArgumentResolver接口,相信机智的道友看名字也猜出来了,这个类其实也处理我们请求的@RequestBody注解哦。

好了,我们来看看它的supportsReturnType()方法。

public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class))
;
}

简单吧,就判断了是否携带@ResponseBody注解。那紧接着就是handleReturnValue()方法咯。

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException
{

// 设置请求已完成的标记
mavContainer.setRequestHandled(true);
// 包装成ServletServerHttpRequest,ServletServerHttpResponse对象
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

// 把返回值写入response的输出流当中
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

writeWithMessageConverters()这个方法中,就会把我们的返回值通过对应的converter转换,然后输出到页面。我们平时配置的jacson组件去处理返回值的解析,都是在这里面。方法太长了,属实不想贴代码,想了解更细的道友就自行研究了哦,你一定可以的!


结语: 这篇虽然已经聊完了请求参数封装和返回值处理的内容,本以为内容不会很多,而实际上超出预期了。本来是在这篇把自定义也一并讲完的,但是限于篇幅原因,自定义就只能留到下一篇了。

上篇所说的不用AOP实现请求日志的功能,只能延迟到下篇结束之后了(在下可没有忘记的哦)等到把这坨聊完,铁定给各位道友安排工作中一定用得着的内容,有关请求日志打印,traceId方法耗时统计的功能,这些功能都有一定的相似性,到时候一并给各位道友安排上!


最后,日常祝各位道友越来越牛逼~

Good Luck~


原文始发于微信公众号(心猿易码):Spring源码解读(第九弹)-请求参数封装,返回值处理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133291.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!