源码解析
版本信息:SpringBoot 2.6.1
每一部分的最后,我都放了所有断点信息的截图,只要你跟着debug下来,相信你一定收获满满
1. 返回值处理源码解析
1.1 测试 controller 编写
我们简单编写一个 controller 里面包含一个查询用户信息的方法,返回一个
@RestController
@Slf4j
public class TestController {
@GetMapping("/user/{id}")
public User user(@PathVariable Integer id) {
log.info("查询用户信息id={}", id);
User user = new User();
user.setName("张三");
user.setAge(18);
return user;
}
}
接下来我们用 postman 发起请求来看一下返回值的处理过程
1.2 源码debug
本次 debug 会直接进入到返回值处理部分,前面的寻找方法处理器、请求参数处理等等在我的另一篇文章中有详细讲解 【SpringBoot2—Web请求处理源码解析】
我们从 org.springframework.web.servlet.DispatcherServlet#doDispatch 开始看
接下来直接来到 ha.handle()
接下来执行 handleInternal()
接下来直接来到 invokeHandlerMethod()
接下来直接来到 invocableMethod.invokeAndHandle()
从这里开始就真正到了返回值处理流程了,invokeForRequest() 执行了目标方法并获得了返回值
接下来判断了我们的返回值是否为空,是否有响应异常的原因
前面的判断通过之后,并且返回值不为空,就到了调用返回值处理器处理返回值的步骤了 this.returnValueHandlers.handleReturnValue()
处理返回值首先要获取到可以处理当前返回值的处理器,调用 selectHandler()
遍历所有的返回值处理器目前看到的有15个,找到合适的处理器
最终我们找到 RequestResponseBodyMethodProcessor 这个返回值处理器,我们的 controller 使用了 @RestController 注解,所以返回值相当于被 @ResponseBody 标注,从这个解析器的名称也可以看出就是用来处理返回值为responseBody 的方法
RequestResponseBodyMethodProcessor 是如何处理的
接下来调用 writeWithMessageConverters()
接下来判断是否为 Resource 类型,我们是 User 类型不符合
判断响应类型是否已经被决定,这里并没有将走 else 里的逻辑内容协商
内容协商
首先获取客户端可以接收的类型为 */* ,可以去查看 postman 的 headers 中的 Accept 值
然后获取服务端可以响应的类型,可以看到目前有4种
使用两层 for 循环找到可以用于响应给客户端的类型
经过内容协商,还有4中类型可以用于响应,因为客户端可接受类型是 */*
由于客户端再发送请求时是可以对每一个类型的响应设置权重的,例如 application/xml;q=0.8 application/json;q=0.9,所以要进行排序
决定了最终的响应类型之后,就会遍历所有的 messageConverters 消息转换器,找到可以将 返回值类型转化为响应类型的转换器,对于本次请求就是找到能够将 User 转换为 application/json
放行断点可以看到最终找到了 MappingJackson2HttpMessageConvert
调用消息转换器的 write() 方法,将响应写出
然后调用 writeInternal()
在 writeInternal() 中使用 ObjectWriter 将返回值写入了流中
可以看到返回值已经被写入到了 outMessage 中
至此对于返回值的处理过程我们就 debug 结束了,本次debug 涉及的断点如下所示