Springboot 2.7.6 升级 3.1.0 爬坑指北

2,495 阅读7分钟

背景

目前项目经过之前的升级已经从 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 后, 则可能会出现自动注入报错问题, 这里有两个解决方案

  1. 寻找依赖 Starter 的更新版本, 以便于其支持新版自动配置
  2. 在自己的项目中添加 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-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>

image.png

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

也希望大家都能持续推动历史项目升级, 享受新技术带来的红利.