背景
目前项目经过之前的升级已经从 2.1.6.RELEASE 爬到了 Springboot 2.7.6, 项目稳定在线上运行了一段时间之后, 决定再开始折腾折腾朝着终极目标 3.x 版本进发, 刚好这几天 Springboot 又正式发布了 3.1.x 版本, 这又激发起了我的升级欲望, 话不多说准备开干.
升级思路
看了下一些三方分享文章 对一些大致的阻断性变更都也进行了说明, 但还是各自项目实际情况不同, 所以还是按照项目依赖先升级 Springboot 基座版本, 再解决各种编译&运行问题, 去一步步解决实际问题
升级 JDK 17
因 Springboot 最大的变动就是 Java 最小支持版本升级到了 17, 因此我们的第一步是下载一个 17 版本的 jdk, 这里还有一个小故事, 因为笔者的电脑是 M1 的 Mac, 因此在安装 jdk 8 还找了三方的 azulu-jdk 以便支持 Arms 版本的处理器, 但 Jdk 17 Oracle 官方已经推出了 Arms 版本的 jdk, 因此可以直接去官方下载即可 www.oracle.com/java/techno…
Springboot 3.x 废弃用法部分改造升级
在 Springboot 3.x 中, 官方对一些在 2.x 系列兼容支持的过时特性进行了废弃移除, 因此在升级前置部分, 我们可以对这块进行前置改造, 以支持 Springboot 3.x 升级
SpringBoot 配置文件改造升级
在 升级 2.7.6 中我们有提到 在 Springboot 2.4 中, Springboot 对配置文件读取做了重大变更, 虽然在 2.x 中提供了兼容性的解决方案, 但 3.x 已被官方移除, 对此我们对自己的项目进行了一次分析, 看如何能够去适配官方新的写法 在新的配置文件读取逻辑中, 官方做到了几个重大的改变, 具体可参考 cloud.tencent.com/developer/a…
- 配置文件的值将会按照文档定义的顺序从前到后读取 - 即定义在前面的属性值将会被在后面的属性覆盖
- profiles 激活开关不能定义在特定环境中 - 无法将 spring.profiles.active 写在非 默认配置文件(application.yml | application.properties) 中
- 添加了 group && 扩展了 import 的作用域 -- 这块主要作用于配置文件分组&外挂配置扩展(k8s 等), 适应云平台一些特性
结合自己的项目, 配置文件主要的作用在于区分环境和抽取一些公共配置文件, 引入基础配置使用了 spring.profiles.include
修改时, 考虑过使用 group 定义配置组来沿用现有配置, 但发现不同环境需要写不同配置组, 比较麻烦, 不太符合配置文件简单明了的设计本意, 因此没考虑
# 项目基础配置
application.yml
# 项目公共配置
application-common.properties
# 项目基础生产配置
application-base-prod.properties
# 项目生产配置
application-prod.properties
最终我们采用, 将 base-prod 融入 prod 中, 简化配置文件数量, 在引入配置上 使用 spring.config.import=classpath:config/application-common.properties 这种新形式 配置改造完成后, 就可以移除 spring.config.use-legacy-processing=true 兼容性开关了 这样在运行时, 也可以沿用 spring.profiles.active 来激活对应的配置文件, 对兼容性较为友好.
# 项目基础配置
application.yml
# 项目公共配置
application-common.properties
# 项目生产配置 + 项目基础生产配置
application-prod.properties
Auto-Configuration 自动配置改造
Springboot 2.7.x 以后官方修改了自动配置的注册地址, 由之前的 spring.factories 修改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports github.com/spring-proj… 另外此部分因为项目依赖的一些底层的 Starter 可能并未完成 Springboot 2.7 + 的适配, 因此在升级 Springboot 3.x 后, 则可能会出现自动注入报错问题, 这里有两个解决方案
- 寻找依赖 Starter 的更新版本, 以便于其支持新版自动配置
- 在自己的项目中添加 AutoConfiguration.imports 文件, 并将底层 Starter 的自动配置写在自己项目中.
这里我们发现的实际例子是 druid-spring-boot-starter, 当时的版本是 1.2.16, 还未适配新版自动注入写法
====before====
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
====after====
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
Mybatis 数据库依赖改造
我们的项目使用了 Mybatis Plus + Dynamic Datasource + Druid 的数据库组合, 而且版本都较老, 还未适配 springboot 2.7 新版自动配置写法等新特性. 因此我们先对版本进行升级支持
- Dynamic Datasource 3.6.0 支持 springboot 新版自动配置写法
- Mybatis Plus 3.5.3 支持 springboot 2.7 + 版本
- Druid 目前还未明确支持 springboot 3.x 版本, 但可以手动添加自动配置文件即可, 可见上文 Auto-Configuration 部分.
<dynamic-datasource-spring-boot-starter.version>3.6.0</dynamic-datasource-spring-boot-starter.version>
<mybatis-plus-boot-starter.version>3.5.3.1</mybatis-plus-boot-starter.version>
升级依赖后, 我们发现 Mybatis Plus 存在一些兼容性变更
- Mybatis 插件写法变更. 3.4.0 以后废弃了自行注入插件的方式, 采用聚合的形式进行处理
// before
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
//添加sql解释器做数据权限
List<ISqlParser> sqlParserList = new ArrayList<>();
paginationInterceptor.setSqlParserList(sqlParserList);
//租户数据权限
sqlParserList.add(new CustomerSqlParser().setTenantHandler(new CustomerHandler()));
return paginationInterceptor;
}
//after
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
CustomTenantLineInnerInterceptor tenantLineInnerInterceptor = new CustomTenantLineInnerInterceptor();
interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
//乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
- Mybatis 废弃注解变更
// before
@SqlParser(filter = true)
// after
@InterceptorIgnore(tenantLine = "true")
- Mybatis mapper xml 关键字冲突, 新增了关键字 ur rs, 是因为升级后, 依赖的 JSqlParser 升级后添加了新的关键字
- count() 返回类型变更, 从 Integer 变为了 Long, 这个部分对代码的改动也比较大
- LambadaWarraper in() 等集合查询参数由数组改为了集合, 这里需要检查下各自代码中的改动
Springboot 3.x 依赖更新改造
升级 jdk 最小编译版本 & spring 依赖底座, 一些其他三方依赖都建议升级到最新, 尽早暴露兼容性问题
<!-- 升级 Jdk 最小依赖版本 为 jdk 17 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!--spring 依赖升级为最新 -->
<springboot.version>3.1.0</springboot.version>
<spring-cloud.version>2022.0.2</spring-cloud.version>
另外很多项目为了适配 springboot 3.x 都提供了一些新的依赖, 例如 druid starter, knife4j. 需要项目负责人按照自己的实际依赖进行修改.
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
Springboot 3.x 代码兼容改造
Javax 迁移 Jakarta 改造
这个部分目前 idea 已支持程序级别重构迁移, 操作路径在 idea -> 重构 -> 迁移软件包和类 -> JavaEE to Jakarta EE 如果项目里面有显示依赖 javax.xxx 等模块, 应在迁移前做好依赖变更
<!-- Before Javax 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- After Jakarta 依赖 -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
Spring cloud Sleuth 迁移 Micrometer 改造
Spring Cloud Sleuth 项目目前只维护到 3.1.7, 只支持到 Springboot 2.x 系列, 项目主页也说明了后续维护迁移到了 Micrometer Tracing 项目中, Micrometer 是致力于一个应用可观测的标准化建设项目 在迁移上, 我们有参考这篇文章 www.baeldung.com/spring-boot… 首先添加 Actuator Starter 和 Mircometer-Tracing-Bridge-Brave 的依赖, Actuator 提供了可观测部分的自动注入, Mircometer-Tracing-Bridge-Brave 则是对 Sleuth 底层 Brave 的依赖传递, 使应用上层无需改动使用方式
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
使用方式demo, 将调用链路 ID 返回到 Response Header 上, 便于前后端对齐调用链路
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 5 + 1)
public class TraceIdAttachFilter extends GenericFilterBean {
private final Tracer tracer;
private String headerName;
TraceIdAttachFilter(Tracer tracer, @Value("${spring.application.name}") String projectName) {
this.tracer = tracer;
this.headerName = "x-" + projectName + "-trace-id";
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Span currentSpan = this.tracer.currentSpan();
if (currentSpan == null) {
chain.doFilter(request, response);
return;
}
// for readability we're returning trace id in a hex form
((HttpServletResponse) response).addHeader(headerName, currentSpan.context().traceIdString());
chain.doFilter(request, response);
}
}
编译运行时报错改造
Jrebel 暂无法使用
用 idea jrebel 无法启动项目, Github 确认是 jrebel 2022.4.2 不支持 jarkata 导致 github.com/spring-proj…
编译运行部分
因为我们修改了项目的最低依赖版本, 因此在一些 CI CD 中, 需要配套升级项目的 jdk 版本和maven版本, 这里我们采用的构建环境是 jdk17.0.6 + maven 3.9.1
结语
以上内容则是我们在升级 Springboot 3.1.0 中遇到的一些问题改造, 这里面还有一些 Maven 依赖的问题, 也花了好久的时间处理, 不过都是实际项目的依赖冲突, 这个就不做分享, 目前项目已经可以在本地运行起来, 也能明显感受到项目启动速度提升.
# Springboot 2.7.6 & jdk8 启动耗时
Started XXXXXApplication in 43.226 seconds
# Springboot 3.1.0 & jdk17 启动耗时
Started XXXXXApplication in 18.628 seconds
也希望大家都能持续推动历史项目升级, 享受新技术带来的红利.