引言
不知道大家是不是也有一种想法,就是喜欢用新的东西。比如:手机系统出现新版本就赶紧升级、软件出现新版本也会在第一时间进行升级。反正我是有这种想法,比较喜欢新的东西,因为新的东西会有更 cool 的特性,可以给人心理、生理上一种舒适感(生理舒适感???)。
背景介绍
公司不同项目使用的 SpringBoot 版本是不同的,最近在做的项目使用的是比较新的版本,2.x。该项目开发过程中,所有对外提供的服务,都会有一个接口层和接口实现层。为了让接口的职责更加的清晰明了,大部分信息都在接口层进行定义。比如:接口的请求映射路径,接口方法接收参数的方式@RequestBody 以及参数校验方式@Valid 等等。后来又去另一个项目组去开发,接到一个需要提供服务接口的任务,当时也没在意,觉得很简单,分分钟就可以完成,悲剧也就在此时此刻发生了。
接下来请大家先看看项目实例代码,在实例中会为大家重现错误,并提供解决方案以及分析出现该问题的原理是为什么。
项目实例
第一步:我们需要定义服务的接口
@RequestMapping("/persons")
public interface PersonApi {
/**
* add
*
* @param person
* @return
*/
@PostMapping("/")
List<Person> add(@Valid @RequestBody Person person);
/**
* update
*
* @param person
* @return
*/
@PutMapping("/")
List<Person> update(@Valid @RequestBody Person person);
}
第二步:有了服务接口,那么肯定会有服务接口的实现
@RestController
public class PersonController implements PersonApi{
private static List<Person> personList = new ArrayList<>();
static {
personList.add(new Person(10001, "test1"));
personList.add(new Person(10002, "test2"));
personList.add(new Person(10003, "test3"));
personList.add(new Person(10004, "test4"));
personList.add(new Person(10005, "test5"));
}
@Override
public List<Person> add(Person person) {
personList.add(person);
return personList;
}
@Override
public List<Person> update(Person person) {
personList.removeIf(p -> Objects.equals(p.getId(), person.getId()));
personList.add(person);
return personList;
}
}
第三步:服务接口编写完成,就需要自己调用接口进行测试,接下来就进行接口测试
可以看到接口可以正常调用,说明接口上使用@RequestBody 起了作用。当然,本文到此还没有结束,好戏还在后面
第五步:开始展示真正的本领了,修改 SpringBoot 的版本号,将版本号修改为 1.5.8.RELEASE
第六步:再次接口调用
可以很神奇的看到,接口请求的参数并没有和我们的接口参数对象进行绑定,也就是我们的@RequestBody 注解不生效了。此时的我整个人都不好了,上一个项目写的好好的接口,到这里就不行了?难道是水土不服?于是打开了另一个项目,一个字母一个字母进行对照,生怕错误了什么,最终还是以我失败宣告结束。
尝试方法
当时是不想看源码的,因为看源码好累,于是我就在实现方法上也加上了同样的注解,准备尝试一下。
@Override
public List<Person> add(@RequestBody Person person) {
personList.add(person);
return personList;
}
不抱希望的尝试请求接口
当返回结果出现在我眼前的时候,只有“我靠”两个字能形容我的心情。突然有一种山重水复疑无路,柳暗花明又一村的感觉,整个世界又变的美好了。
虽然问题得到了完美解决,但是内心深处还是想了解下到底是为什么?只有弄清原理,下次在遇到此问题的时候,我们才可能迎刃而解,不费吹灰之力。接下来就为大家揭开这层神秘的面纱!
疑惑解答
SpringBooot1.5.8.RELEASE 源码分析
打开 RequestMappingHandlerAdapter#invokeHandlerMethod,找到如下代码
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
一直到 MethodParameter#getParameterAnnotations
如上代码获取当前方法参数的注解信息,最终返回,重点:此处获取的是实现方法上的注解,并不会获取接口方法上的注解,所以必须在实现方法上加上注解。
SpringBooot2.2.0.RELEASE 源码分析
跟着上面的思路找到获取参数注解的方法
可以很清楚的看到升级后的 SpringBoot 的 HandlerMethodParameter 中重写了获取参数注解的方法,重写方法中调用了父类获取参数注解的方法,并且在自己的实现中又去获取了接口上的注解,然后进行组合。因此升级后的项目中可以在接口上定义注解,同样也可以在实现方法上定义注解。
此次的分享到此已经结束了,后续还会继续为大家带来精彩对决的吐血经历,让撸友多点发质,多点开心。
参考文献
- 搭上 SpringBoot 请求处理源码分析专车
- 搭上 SpringBoot 参数解析返回值处理源码分析专车