SpringBoot 1.X到2.X 升级的一些思考总结

1. 为什么要升级

  1. 支持最新的Java9(虽然目前的项目还没有用到Java9,但未来升级到Java9的可能性会很高)。
  2. 基于Spring5构建,Spring5的各种新特性均可以在这里使用。
  3. 为各种组件的响应式变成提供自动化配置(虽然我们没有用到,但使用这些组件的人用起来会更顺畅)。
  4. 支持SpringMVC的非阻塞式替代方案WebFlux以及嵌入式NettyServer。
  5. 最重要的还是目前的项目依赖还比较简单。如果现在不升级,随着依赖数的增加、版本跨度增加,再升级依赖就会更加困难。

2. 升级用到的工具

2.1 mvn命令

在排查依赖间影响的时候,最常使用的还是mvn命令。

2.1.1 mvn基本命令

如果要查看当前的依赖树,可以使用以下命令。

1
mvn dependency:tree

2.1.2 mvn查看重复忽略的依赖

如果要查看maven如何解决包冲突,即查看重复的、被忽略的依赖,可以使用以下命令:

使用上述命令后,会看到有些依赖上会有额外的信息。他们的含义分别是:

  1. 最后写着compile的就是编译成功的。
  2. 最后写着omitted for duplicate的就是有jar包被重复依赖了,但是jar包的版本是一样的,该行依赖被忽略。
  3. 最后写着omitted for conflict with xxxx的,说明和别的jar包版本冲突了,而该行的jar包不会被引入。
  4. 最后写着version managed from xxx ;omitted for duplicate ,表示虽然pom依赖中写明是依赖xxx,但当前项目明确指定使用该行前面的版本,最终由于版本重复当前行被忽略。
  5. 最后写着version managed from 1.16.8 ;表示虽然pom依赖中写明是依赖xxx,但当前项目明确指定使用该行前面的版本。

2.1.3 mvn指定或排查特定依赖

大型项目中,由于依赖众多,当使用上述命令后,查看的信息会特别多。因此可以使用以下命令,在查看依赖情况时指定要查看或过滤的包。

指定查看的包:

1
mvn dependency:tree -Dverbose -Dincludes=org.springframework:spring-tx

指定排除的包:

1
mvn dependency:tree -Dverbose -Dexcludes=com.google.guava:guava

过滤串使用groupId:artifactId:version的方式进行过滤,可以不写全。

2.2 maven仓库

查看公有包可以访问 MvnRepository ,当你需要升级依赖的时候,可以在这个网站上查看想升级版本的包的最新版本信息。

3. 升级中可能遇到的问题

依赖版本升级可能会遇到以下几种问题,假设我们有A/B/C/D等包。

3.1 多个依赖连续升级

假设在A包中有一个类ClassA,被B包引用。由于升级,ClassA改了路径或名称、或者由别的类代替。那么B包也需要升级,以匹配A包升级。同样的,如果B包中的类被C包依赖,C包也需要升级。在这个过程中,如果B包的版本中没有适配A包最新版本的话,可以尝试在代码中重写一个旧的ClassA,让B包能够用上。如果改动是在太大,那么建议还是别把A的版本升的太高。

3.2 新版本依赖项减少

假设在A包中,原先有依赖google-guava包,然后我们自然而然地用了guava包中的类。在A包升级后,它去掉了对google-guava的依赖,那我们的项目可能就会报错了。这里建议凡是在业务代码中用到的第三方包,都在pom中指明依赖。

3.3 所升级包管理混乱

不知道大家有没有遇到过包版本混乱的情况?我遇到过,在2018-01版本中,某些接口可以使用,在2018-02版本中又不可以。你以为他的包版本规则是按照日期产出的,结果又出来一个1.2.10版本。总之,包的开发者对包的管理存在一定的问题,这个时候,最好和包管理者进行沟通,确认可用版本。

3.4 升级后配置发生改变

所省级包中的某些类在升级后,入参、属性发生变化,从而导致代码或配置报错。

在springboot1x升级2x时,有很多在application.properties中的配置key也会发生变化。一般情况下,根据idea的提示修改即可。

在springboot1x升级2x时,HttpMessageConverters从web模块迁移至了http模块,如果有使用HttpMessageConverters这个类,就需要做一下调整。WebMvcConfigurer的跨域配置,原先只需要调用addMapping方法即可,升级后就需要指定allowCredentials了。

3.5 升级后的框架代码逻辑冲突

假设A是一个框架包,某个接口允许有一个实现。结果在升级B/C包后,B/C包都实现了这个接口。在编译时,没有任何问题,但运行时,A框架发现有两个实现,所以会报错。 此时,需要查看B,C包在升级后,是否还同时需要,如果不同时需要,则只要去掉其中一个依赖即可。如果同时需要,则需要联系包管理者进一步解决冲突。

4. 升级后可能遇到的问题

在升级完包依赖后,需要我们进一步观察升级对生产造成的影响。一般来说,可能会有以下这些问题。

4.1 bug修复引发的bug

在我们的代码中,玩的6的人总喜欢用一些奇奇怪怪的高级方法,例如反射取一下私有属性等等。但随着依赖包的升级,有些私有属性消失了或bug被修复了,我们代码中的一些高级用法就会失效,严重者甚至会引起线上问题。

4.1.1 mybatis升级带来的问题

大家都用过mybatis吧,在if的test中写入这种语句for_test == null or for_test='' or for_test == -1。在特定版本之前,由于ognl的bug,等同于for_test == null or for_test=='' or for_test == -1,该语句可以按正常逻辑走通,但由于升级了mybatis,ognl修复了该bug,该语句就可能引发了一些业务错误。

4.1.2 tomcat升级带来的问题

大家也都用过tomcat吧,ParameterMap (Tomcat API Documentation)是request中的一个属性。我们为了在进入业务控制器前覆盖/修改前端传入的参数,可以向这个map传入一些值。

虽然官方文档告诉我们,该map只能读不能写。但实际上,在tomcat特定版本前,在filter中使用RequestDispatcher的forward进行请求转发,tomcat会使用ApplicationHTTPRequest对原始的request进行包装,而在ApplicationHTTPRequest的getParameterMap方法中,返回的是普通的HashMap。

该问题的具体修复版本是:7.0.68和8.0.14。在升级到这些版本后,这个特性就不能使用了,从而会导致业务错误。我当时的临时解法是判断getParameterMap方法得到的是不是可写的map,如果不是,就利用反射,将该map变成可写的。

4.1.3 小结

以上两个case,第一个是误写,第二个是不了解接口说明。在我们的平常开发中,尽量避免这两种问题吧。

4.2 性能上的影响

虽然在常识中,包越升级越好。但也有可能升级后的包存在某些bug,导致对生产环境产生影响。因此对于重要应用,在升级完包后,最好预发环境做好充足验证,在发布上线前,也要做好beta验证。