一、概述
鉴于 JDK8 已经是老古董,还有性能问题,兼且各个公司已经不再维护 1.8 的 JDK ,所以升级 公司的核心产品之一 的后端到 JDK 到 17 是相对要紧的事情。
通过升级到 jdk17 ,具有以下好处:
- 不要在头疼同时适应两个 jdk , 放下适应 JDK8 的负担
- 在生产环境基本上只需要部署一个 jdk 即可
- 具有更好的性能
- 能够利用上更好更新的组件版本。例如 springboot3,spring6.x都是基于jdk17的。
- 更好的安全性。这对于项目很重要。因为许多客户会安排安全测试,过时的jdk是一个不好解决的问题。升级到jdk17能够更好解决这个问题
- 更好的竞争能力。当我们的核心jdk是17的时候,毫无疑问比那些还沉滞在jdk8的竞争对手更好,尤其是功能相差不大的情况下。
本次升级后端,大概耗费了一周的时间,其次 httpsecurity 耗费了比较多的时间。
整体上,还算顺利。
二、步骤详情
总体上遵循以下步骤:
(1) 升级准备
(2) 确定 spring 组件版本
到 spring.io 上看了下,选择 springboot-3.3.0
(3) 确定其它组件版本
(4) 升级有关代码
(5) 调整其它配置
(6) 解决有关异常
(7) 测试
(8) 完成升级
2.1. 升级准备
由于 这 是一个大的版本升级,所以需要做以下准备:
(1) 确认是否能升级
除了前文提到的原因,还需要确定当前这个产品是否可以升级,毕竟 JDK17 和 JDK1.8 不一样,且升级了 JDK17 后,有关的组件都要一起升级(是否有相关的版本,相关版本是否稳定 ?)
考虑到 我们的产品 没有使用太多的三方软件,即使有,也都是流行的
现在 jdk17 都已经发布了快 3 年了;有很多其它公司也升级到了 jdk17;部分公司已经把他们的产品升级到JDK21了。
结合这些因素, 产品升级 到 jdk17 不存在技术障碍!
之所以没有考虑立刻升级到JDK21,是因为其它很多产品都在JDK17,其次一次性到21,没有那么大把握。步子太大,会不会扯蛋了?
虽然官方的文档说springboot3.3.x支持JDK22。但是由于三方组件的存在,导致不敢一次性迈出太大的步子。
(2) 在 git/svn 上开一个分支,或者直接开一个新的仓库 , 不要影响现有的主干代码
2.2. spring 组件版本
在 spring.io 上可以看到可用的版本,本着使用最新可用版本的原则,选择了 :
- springboot-3.3.1 (ga)
- spring-6.1.10 这是 springboot 限定的版本,所以只需要选择 springboot 版本即可
按照 spring惯例 ,当升级的时候,通常相关的组件都是一期升级的,所以总的来说,只要指定 springboot 版本即可。
2.3. 确定其它组件版本
分类 |
组件 |
功能描述 |
旧版本 |
升级 |
新版本 |
说明 |
数据存取 |
druid-spring-boot-starter |
数据连接和连接池管理 |
1.2.11 |
是 |
1.2.23 |
核心组件,必须升级 |
jdbc驱动 |
* |
jdbc连接 |
|
否 |
|
主要看各个厂家,考虑到 jdbc驱动都是比较成熟的,在jdk17中运行,问题应该也不大 |
消息队列 |
org.apache.rocketmq/rocketmq-client |
amqp |
5.1.0 |
否 |
|
暂时不升级,这个需要较长时间的测试 |
http请求 |
httpclient,httpcore |
rest请求 |
4.5.13 |
是 |
5.3.1/5.2.4 |
原来是: org.apache.httpcomPONEnts/httpclient 现在是: org.apache.httpcomponents.client5/httpclient5 |
http |
javax.servlet/javax.servlet-api |
servlet |
4.0.1 |
否 |
|
移除 |
http |
jakarta.servlet/jakarta.servlet-api |
servlet |
6.0.0 |
否 |
|
新增。用于替代 javax.servlet-api |
JSON |
fastjson2 |
JSON |
2.0.32 |
是 |
2.0.51 |
fastjson2bug较多,尽可能升级下 |
JSON |
com.jayway.jsonpath/json-path |
JSON路径分析 |
2.8.0 |
否 |
|
|
ORM |
mybatis-spring-boot-starter |
orm |
2.2.2 |
是 |
3.0.3 |
不升级会导致 mybatis有关bean初始化异常 |
ORM |
pagehelper-spring-boot-starter |
分页 |
1.4.3 |
是 |
2.1.0 |
被 mybatis依赖 |
ORM |
jsqlparser |
sql解析 |
4.2 |
是 |
4.7 |
被 pageHelper依赖 |
XML |
javax.xml.bind/jaxb-api |
XML 解析 |
2.3.1 |
否 |
|
暂时不可替代,不可删除,也不需要升级 |
文档 |
swagger |
文档 |
|
否 |
|
从现有版本移除 |
定时 /调度 |
quartz |
定时 /调度 |
2.3.2 |
否 |
|
|
通用工具 |
org.apache.commons/common-lang3 |
|
3.12.0 |
是 |
3.14.0 |
|
通用工具 |
org.apache.commons/commons-pool2 |
|
2.9.0 |
是 |
2.12.0 |
|
通用工具 |
commons-io/commons-io |
|
2.11.0 |
否 |
|
|
通用工具 |
commons-fileupload/commons-fileupload |
|
1.4 |
否 |
|
|
存储 |
minio |
|
8.2.1 |
否 |
|
|
编译 |
maven-compiler-plugin |
编译 |
3.1 |
是 |
3.13.0 |
|
注:
- 主要考虑到核心组件即可,其它的小组件遇到了再解决。
- 有什么版本可用,可以访问 https://mvnrepository.com ,或则各个组件官网(一般是 GitHub), 或者是国内镜像网站,例如阿里的 https://developer.aliyun.com/mvn/search
- 部分组件版本必须在升级中调试后才可以确定
2.4. 升级有关代码
当更换了以下组件之后,需要尽快修改代码,修改的原因主要包含:
(1) 配置变更
主要是 spring 升级导致,可能需要修改配置。当然也可能是其它的组件
(2) 包路径变更
(3) 方法不存在
(4) 方法过时
这个需要特别注意 - 如果可能应该尽量把过时的方法移除掉,替换为正常的方法。
2.4.1. 修改 yml 配置
- 修改范围 -spring.redis 修改为 spring.data.redis-- 这是 spring 要扩大 spring.data 的范围
在spring.data的域名之下,有很多的内容,远不止redis.除了基本的JDBC,还有Rest,elasticsearch,jpd,ldap等等。
- 添加参数( bean 相关 )
spring: main: allow-circular-references: true allow-bean-definition-overriding: true |
在 spring6.1.10 中,这两个属性默认是 false. 如果你的项目不存在循环引用,或者覆盖定义的情况,那么可以不添加 .
- 移除配置
移除 swagger 配置 - 这个太垃圾,过分入侵,还浪费了自有的注释,增大程序员的工作量
希望有直接能够利用javaDoc的类似组件。
2.4.2. java 基础类型有关的
基础类型主要指 Integer,Long,BigDecimal,BigInteger 等等。
在 jdk17 中,许多方法已经被标注为过时 (deprecated) 。
(1) java 基类 new Class("xxx") 需要修改为 Class.valueOf("xxx")
new Long("xx"),new Integer("xxx"),new Byte("xx"),new Short("")
这些都要修改为对应的 valueOf("xxx") 。
jdk 这么做,主要是出于性能考虑,尤其针对 Integer 。
(2) Class.newInstance()
需要把这个替换为 getDeclaredConstructor().newInstance()
(3) Spring.Base64Util 过时,改用 apache 的 Base64
(4) ruoyi 自身的 Base64, 移除掉,避免和 apache 的冲突。
(5) ruoyi 自身的 md5Util 移除
从 spring 自身的改变来看, spring 也逐渐向 java 标准和阿帕奇基金会靠近,一个是为了标准,其次是避免浪费
时间,最后是不要给 spring 用户带来困扰。 spring 只要做好自己的就行了。
ruoyi如果用于项目还是可以的,但是用于产品开发还是需要进行较多的改造。 因为产品要求更高的安全、适应度、性能等。
2.4.3. servlet 相关
由于在 jdk17 中移除了 javax 的部分包,所以 很多 javax.xxx 都需要修改 jakarta.xxx
这里主要包含:
(1) javax.servlet
(2) javax.annotation
其它 javax.net,javax.sql 等则继续保留着。
2.4.4. httpclient 相关
具体略,总之需要修改。
httpclient5 有重大变更:支持 http2, 异步支持,更好的连接池等
2.4.5. spring-security
这个改变比较大,在 spring6.x 主要通过注解和定义 bean 来实现 spring-security 配置,而在 5.x 中,则是通过扩展 WebSecurityConfigurerAdapter 来实现。
@Configuration @EnableWebSecurity @EnableGlobalAuthentication @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig { }
|
注意,不要去 override 已有的实现,否则配置还是比较麻烦的。
spring 的思路就是你可以改配置,改零件,但是不要改核心。如果要改核心,那么太费劲了。
在这个类中实现以下几个 bean 即可:
- AuthenticationProvider
- AuthenticationManager
- SecurityFilterChain
- BCryptPasswordEncoder
其中 SecurityFilterChain 是关键,这里主要配置白名单 。
另外一个变化是,禁用了默认的 logout, 而是新增了一个 /logout 接口:
/** * 执行退出 * @param request * @return * @since 1.5 */ @PostMapping("/logout") public AjaxResult logout(HttpServletRequest request) { try { LoginUser user=SecurityUtils. getLoginUser (); String key=CacheConstants. LOGIN_TOKEN_KEY + user.getToken(); redisCache.deleteObject(key); return AjaxResult. success (); } catch (Exception e) { //如果有异常,则证明已经退出了,不要阻拦 return AjaxResult. success (); } } |
2.4.6. jsqlparser
主要是因为 pageHelper 升级了。
当然产品 本身也有用到 jsqlparser 。
2.5. 调整其它配置
主要是编译方面的配置。
由于升级了 jdk ,包括核心组件 maven-compiler-plugin , 所以有些原来的默认设置需要进行调整。
2.5.1. 调整编译选项
在 eclipse 中,其实只需要设置 pom.xml 中配置即可,无需修改工程的环境配置。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.13.0</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> < parameters >true</ parameters > </configuration> </plugin> |
红色部分添加上去,并设置为 true 。
如果不添加这个,那么 spirng 中很多需要通过反射获取信息的方法可能存在问题。
因为这个选项会让 java 把 .java 编译为 .class 的时候,保留方法的名称,而不是把方法名称随意修改为不认识的名称。
2.6. 解决有关异常
2.6.1. bean 异常
2.6.1.1. 循环引用和覆盖
如前,主要新版本中,有些参数修改了默认值,所以修改如下:
spring: main: allow-circular-references: true allow-bean-definition-overriding: true |
2.6.1.2. @Primary 问题
当有多个 Datasource 类型的 Bean ,或者类似其它的,则必须为 Bean 添加 @Primary 的注解,否则回报告异常。
Parameter 0 of method sqlSessionFactory in com.ruoyi.framework.config.db.MyBatisConfig required a single bean, but 3 were found:
触发异常的具体代码如下:
而在以前的版本中不存在这个!
解决方式有两个:
(1) 在参数上简单添加 @Qualifier("masterDataSource") -- 解决了 mybatis, 但是还要解决 quartz 等等。放弃这个方法
(2) 直接修改定义 DataSource 的地方,为主 bean 添加 @Primary ,就用这个✔
2.6.2. jackson 序列化异常
无法处理 key 类型不是 String 类型 , 例如如果是以下类型的 JSON
{ "batchDetail": { 1: "good" } } |
在序列化的时候会报告异常:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
必须自定义 HttpMessgeConverter ,以便可以自定义处理这种类型的 key 。
但这会导致自定义白名单功能错误,所以还需要调整白名单功能
2.6.3. factoryBeanObjectType 异常
经过定位,这是 mybatis 没有升级导致的。
注:一开始的时候,并没有立刻要升级 mybatis ,虽然意识到了,但是并没有那么做。
2.7. 测试
(1) 每个地方都需要测试
(2) 反复测试
这是总的原则。
测试需要持续较长时间,严格而言,需要再考费一个月左右。
从目前来看,总体是可用!
从性能上看,JDK17的程序的确响应更快一些,从页面的响应也可以看出来!
2.8. 完成升级
完成升级后,关闭原来 git 上代码的权限,设置为只读,并通知有关人。