引言

不知道大家是不是也有一种想法,就是喜欢用新的东西。比如:手机系统出现新版本就赶紧升级、软件出现新版本也会在第一时间进行升级。反正我是有这种想法,比较喜欢新的东西,因为新的东西会有更 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;
    }
}

第三步:服务接口编写完成,就需要自己调用接口进行测试,接下来就进行接口测试

springboot 版本兼容的依赖 怎么看 springboot稳定版本_springboot

可以看到接口可以正常调用,说明接口上使用@RequestBody 起了作用。当然,本文到此还没有结束,好戏还在后面

第五步:开始展示真正的本领了,修改 SpringBoot 的版本号,将版本号修改为 1.5.8.RELEASE

第六步:再次接口调用

springboot 版本兼容的依赖 怎么看 springboot稳定版本_springboot_02

可以很神奇的看到,接口请求的参数并没有和我们的接口参数对象进行绑定,也就是我们的@RequestBody 注解不生效了。此时的我整个人都不好了,上一个项目写的好好的接口,到这里就不行了?难道是水土不服?于是打开了另一个项目,一个字母一个字母进行对照,生怕错误了什么,最终还是以我失败宣告结束。

尝试方法

当时是不想看源码的,因为看源码好累,于是我就在实现方法上也加上了同样的注解,准备尝试一下。

@Override
public List<Person> add(@RequestBody Person person) {
    personList.add(person);
    return personList;
}

不抱希望的尝试请求接口

springboot 版本兼容的依赖 怎么看 springboot稳定版本_spring_03

当返回结果出现在我眼前的时候,只有“我靠”两个字能形容我的心情。突然有一种山重水复疑无路,柳暗花明又一村的感觉,整个世界又变的美好了。

虽然问题得到了完美解决,但是内心深处还是想了解下到底是为什么?只有弄清原理,下次在遇到此问题的时候,我们才可能迎刃而解,不费吹灰之力。接下来就为大家揭开这层神秘的面纱!

疑惑解答

SpringBooot1.5.8.RELEASE 源码分析

打开 RequestMappingHandlerAdapter#invokeHandlerMethod,找到如下代码

modelFactory.initModel(webRequest, mavContainer, invocableMethod);

一直到 MethodParameter#getParameterAnnotations

springboot 版本兼容的依赖 怎么看 springboot稳定版本_程序员_04

如上代码获取当前方法参数的注解信息,最终返回,重点:此处获取的是实现方法上的注解,并不会获取接口方法上的注解,所以必须在实现方法上加上注解。

SpringBooot2.2.0.RELEASE 源码分析

跟着上面的思路找到获取参数注解的方法

springboot 版本兼容的依赖 怎么看 springboot稳定版本_程序员_05

可以很清楚的看到升级后的 SpringBoot 的 HandlerMethodParameter 中重写了获取参数注解的方法,重写方法中调用了父类获取参数注解的方法,并且在自己的实现中又去获取了接口上的注解,然后进行组合。因此升级后的项目中可以在接口上定义注解,同样也可以在实现方法上定义注解。

此次的分享到此已经结束了,后续还会继续为大家带来精彩对决的吐血经历,让撸友多点发质,多点开心。

参考文献

  • 搭上 SpringBoot 请求处理源码分析专车
  • 搭上 SpringBoot 参数解析返回值处理源码分析专车