记一次框架升级

     背景

        随着公司业务的不断扩展,新技术的更新换代,企业内部免不了会对软硬件进行升级,淘汰老旧的组件和实现方案,更新一波技术栈。这不,最近我们公司就面临这么一个难题:旧版本的组件上发现漏洞,为了修复系统漏洞,不得不对它进行版本升级。而升级某个组件时,又会因为版本不兼容,配置更新等原因,必须同步调整其他的组件或配置。因此,看似简单的升级一个组件,却像是推倒了多米诺骨牌似的,越改越多,越理越乱。而受影响的项目又多,每个项目都得有技术人员花费大量时间去处理这种廉价而繁琐的事情,事倍而功半。公司的CTO知道这事后,觉得长痛不如短痛,被动防守不如主动出击,咱干脆借着处理这件事,来一次架构升级,把SpringBoot改成SpringBoot3,JVM从1.8升到17,把公司该升的组件都升了。

        而不才,就悲催荣幸的被委予此项重任。

      框架升级

      一、需求调研

        框架是为了业务需求而服务的,一个好的框架能为整个研发团队降本增效,用更便利,更优雅的方式实现需求。 因此,在升级框架之前,必须得进行一轮需求调研。我联系了公司几个产品线的Leader,通过收集当前产品线所用框架中包含的组件,以及公司的业务诉求,推导出了新框架的建设清单:

新框架技术栈版本类别说明当前现状
JDK17后端Java开发环境JDK 1.8
Spring Boot3.1.9后端后端研发框架Spring Boot 2.1.6.RELEASE
Spring Cloud Alibaba2022.0.0.0后端微服务框架Spring Cloud 2.1.2.RELEASE
Nacos 2.3.2中间件配置中心 & 注册中心Apollo 1.1.0+Eureka2.1.2.RELEASE
RocketMQ5.1.0中间件消息队列不涉及
Sentinel1.8.6开源基础组件服务保障/容错不涉及
XXL-JOB2.4.0中间件定时任务XXL-JOB 2.4.1-SNAPSHOT
Spring Cloud Gateway4.0.6中间件服务网关3.0.6
seata1.6.1中间件分布式事务不涉及
DMV8数据库数据库服务器MYSQL
Druid1.2.22基础组件JDBC 连接池、监控组件Druid 1.1.10
Dynamic DataSource4.3.0基础组件动态数据源,可以实现数据源动态切换不涉及
Redis6.2.7数据库key-value 数据库6.2.7
redisson3.24.3工具Redis 客户端redisson 3.11.2
hibernate-validator8.0.1基础组件参数校验组件hibernate-validator 6.0.17 Final
flowable7.0.0基础组件工作流引擎flowable 6.8.0
knife4j4.4.0基础组件Swagger 增强 UI 实现Swagger1.7.0
Apache SkyWalking 9.7.0中间件分布式应用追踪系统不涉及
Plumelog3.5中间件统一日志平台Logstash 5.3
fastjson22.0.50基础组件JSON 工具库fastjson2 1.2.13
MapStruct1.5.5.Final基础组件Java Bean 转换MapStruct 1.5.2.Final
Project Lombok1.18.30基础组件消除冗长的 Java 代码Project Lombok 1.18.8
JUnit4.13.2基础组件Java 单元测试框架 4.12
mockito5.7.0基础组件Java Mock 框架不涉及
Mybatis-Plus3.5.5基础组件数据库操作组件Mybatis-Plus 3.4.6,tk.mybatis 2.1.5
Nginx1.24中间件反向代理,负载均衡,请求转发Nginx 1.13.7
poi5.2.5基础组件文档在线处理组件poi 4.1.0
easyexcel3.3.4基础组件excel在线处理组件easyexcel 2.1.6
codec1.17.0基础组件数据编码解码组件不涉及
hutool5.8.27基础组件开源工具包hutool 5.3.0
commons-net3.11.1基础组件Ftp连接组件不涉及
beanutils1.9.4基础组件Java基础类库beanutils 1.9.3
aviator5.4.1基础组件表达式处理组件不涉及
jjwt0.12.5基础组件数字签名组件jwt 0.9.0
OAuth26.3.1中间件认证服务OAuth2 2.3.3.RELEASE
minio8.5.10基础组件对象存储服务器与新框架一致
pagehelper2.1.0基础组件分页组件与新框架一致

        在规划这些组件/依赖的版本时,我直接根据名字去https://mvnrepository.com/中查询当前组件的最高版本,毕竟JDK,Springboot都是用的比较新的版本,完全Hold住……于是后面就踩坑了,至于是什么坑,请看下章分解。

     二、父工程搭建

      有了上面的清单,就可以搭建父工程了,父工程其实很简单,一个pom.xml足矣。而pom.xml里的内容,就是上面的清单的具象化。如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <artifactId>leixi-hub-parent</artifactId>
  <packaging>pom</packaging>
  <name>leixi-hub-parent</name>
  <description>leixi-hub 父工程</description>
  <groupId>com.leixi.hub</groupId>
  <version>${leixi-hub.version}</version>

  <properties>
    <!--  按字母顺序  -->
    <apm-toolkit-trace.version>6.5.0</apm-toolkit-trace.version>
    <leixi-hub.version>1.0.0-SNAPSHOT</leixi-hub.version>
    <commons-beanutils.version>1.9.4</commons-beanutils.version>
    <commons-io.version>2.15.0</commons-io.version>
    <commons-lang.version>2.6</commons-lang.version>
    <commons-codec.version>1.17.0</commons-codec.version>
    <dm8.version>8.1.1.193</dm8.version>
    <druid.version>1.2.22</druid.version>
    <discovery.version>6.21.0</discovery.version>
    <dynamic.datasource.version>4.3.0</dynamic.datasource.version>
    <ehcache.version>2.10.6</ehcache.version>
    <fastjson.version>2.0.50</fastjson.version>
    <grpc-spring-boot.version>3.0.0.RELEASE</grpc-spring-boot.version>
    <grpc-bom.version>1.60.1</grpc-bom.version>   <!--用于构建分布式系统中的服务和客户端-->
    <protobuf-bom.version>3.25.2</protobuf-bom.version>   <!--是一个 Maven BOM(Bill of Materials)文件,用于管理 Protocol Buffers(protobuf),-->
    <hutool.version>5.8.27</hutool.version>
    <kaptcha.version>2.3.2</kaptcha.version>
    <java.version>17</java.version>
    <jdom.version>1.1</jdom.version>   <!--操作xml的组件,考虑去掉-->
    <jakarta-persistence-api.version>3.1.0</jakarta-persistence-api.version>   <!--一个 Java 规范,用于定义和管理对象关系映射?-->
    <jackson.version>2.16.1</jackson.version>   <!--用于提供对 JSR-310(Java 日期和时间 API)的支持-->
    <junit.version>4.13.2</junit.version>
    <jetcache.version>2.7.5</jetcache.version>
    <knife4j.version>4.4.0</knife4j.version>
    <lombok.version>1.18.30</lombok.version>
    <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    <micrometer.version>1.12.2</micrometer.version> <!--  micrometer-core 是一个用于度量和监控应用程序的 Java 库。收集和报告与应用程序性能相关的指标和度量-->
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <mybatis-spring.version>3.0.2</mybatis-spring.version>
    <mybatis-plus.version>3.5.5</mybatis-plus.version>
    <mapstruct.version>1.5.5.Final</mapstruct.version>
    <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <poi.version>5.2.5</poi.version>
    <page-helper.version>2.1.0</page-helper.version>
    <plumelog.version>3.5.3</plumelog.version>
    <pinyin4j.version>2.5.0</pinyin4j.version>  <!--用于将汉字转换为拼音-->
    <!--  消息队列  -->
    <rocketmq-spring.version>2.2.3</rocketmq-spring.version>
    <rocketmq.version>5.1.0</rocketmq.version>
    <rocketmq.spring.client.version>5.0.5</rocketmq.spring.client.version>
    <redisson.version>3.24.3</redisson.version>
    <spring-boot.version>3.1.9</spring-boot.version>
    <spring-cloud.version>2022.0.3</spring-cloud.version>
    <spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>
    <springdoc.openapi.version>2.2.0</springdoc.openapi.version> <!-- 用于在 Spring Web MVC 中生成和展示 OpenAPI 文档的库-->
    <tika.version>1.21</tika.version>   <!--用于提取和解析各种文档格式的内容-->
    <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> <!--用于在多线程环境中传递线程本地变量的工具类-->
    <!--  Job 定时任务相关  -->
    <xxl-job.version>2.4.0</xxl-job.version>
    <sentinel-core.version>1.8.6</sentinel-core.version>
    <hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
    <easyexcel.version>3.3.4</easyexcel.version>
    <commons-net.version>3.11.1</commons-net.version>
    <aviator.version>5.4.1</aviator.version>
    <jjwt.version>0.12.5</jjwt.version>
    <minio.version>8.5.5</minio.version>
    <mockito-core.version>5.12.0</mockito-core.version>
    <flowable-engine.version>7.0.0</flowable-engine.version>

  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.skywalking</groupId>
        <artifactId>apm-toolkit-trace</artifactId>
        <version>${apm-toolkit-trace.version}</version>
      </dependency>
      <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>${commons-beanutils.version}</version>
      </dependency>
      <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>${commons-io.version}</version>
      </dependency>
      <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>${commons-codec.version}</version>
      </dependency>
      <dependency>
        <groupId>com.dameng</groupId>
        <artifactId>DmJdbcDriver18</artifactId>
        <version>${dm8.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-3-starter</artifactId>
        <version>${druid.version}</version>
      </dependency>
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
        <version>${dynamic.datasource.version}</version>
      </dependency>
      <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>${jackson.version}</version>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
      </dependency>
      <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>${hutool.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>${tika.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
      </dependency>
      <dependency>
        <groupId>org.jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>${jdom.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>${fastjson.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2-extension</artifactId>
        <version>${fastjson.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2-extension-spring6</artifactId>
        <version>${fastjson.version}</version>
      </dependency>
      <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-bom</artifactId>
        <version>${grpc-bom.version}</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
      <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-bom</artifactId>
        <version>${protobuf-bom.version}</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
      <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-spring-boot-starter</artifactId>
        <version>${grpc-spring-boot.version}</version>
      </dependency>
      <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-client-spring-boot-starter</artifactId>
        <version>${grpc-spring-boot.version}</version>
      </dependency>
      <dependency>
        <groupId>net.devh</groupId>
        <artifactId>grpc-server-spring-boot-starter</artifactId>
        <version>${grpc-spring-boot.version}</version>
      </dependency>
      <!-- 适配grpc-spring-boot-starter -->
      <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-core</artifactId>
        <version>${micrometer.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-client-java</artifactId>
        <version>${rocketmq.spring.client.version}</version>
        <exclusions>
          <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
      <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
        <version>${knife4j.version}</version>
      </dependency>
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-mapstruct-binding</artifactId>
        <version>${lombok-mapstruct-binding.version}</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>${spring.cloud.alibaba.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>${springdoc.openapi.version}</version>
      </dependency>

      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <!--  解决 ThreadLocal 父子线程的传值问题  -->
        <version>${transmittable-thread-local.version}</version>
      </dependency>

      <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis-spring.version}</version>
      </dependency>
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>${mybatis-plus.version}</version>
      </dependency>
      <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>${page-helper.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${mapstruct.version}</version>
      </dependency>
      <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>${jakarta-persistence-api.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alicp.jetcache</groupId>
        <artifactId>jetcache-starter-redis</artifactId>
        <version>${jetcache.version}</version>
      </dependency>
      <dependency>
        <groupId>com.github.penggle</groupId>
        <artifactId>kaptcha</artifactId>
        <version>${kaptcha.version}</version>
      </dependency>
      <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>${ehcache.version}</version>
      </dependency>
      <dependency>
        <groupId>com.belerweb</groupId>
        <artifactId>pinyin4j</artifactId>
        <version>${pinyin4j.version}</version>
      </dependency>
      <dependency>
        <groupId>com.plumelog</groupId>
        <artifactId>plumelog-logback</artifactId>
        <version>${plumelog.version}</version>
      </dependency>
      <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>${redisson.version}</version>
      </dependency>
      <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>${xxl-job.version}</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-core</artifactId>
        <version>${sentinel-core.version}</version>
      </dependency>

      <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>${hibernate-validator.version}</version>
      </dependency>

      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>${easyexcel.version}</version>
      </dependency>
      <dependency>
        <groupId>commons-net</groupId>
        <artifactId>commons-net</artifactId>
        <version>${commons-net.version}</version>
      </dependency>
      <dependency>
        <groupId>com.googlecode.aviator</groupId>
        <artifactId>aviator</artifactId>
        <version>${aviator.version}</version>
      </dependency>
      <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>${jjwt.version}</version>
      </dependency>
      <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>${minio.version}</version>
      </dependency>
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>${mockito-core.version}</version>
        <scope>test</scope>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.flowable/flowable-engine -->
      <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-engine</artifactId>
        <version>${flowable-engine.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

        其实在搭建父工程时,我并不太理解这个父工程的意义,感觉有它没它区别不大。但是在撰写这篇博客的背景章节时,我幡然醒悟,父工程不就是整理了一套可以搭配起来使用的组件及版本清单吗?有了父工程做背书,子工程只需要根据自己的需求引入父工程指定的依赖即可,而不用担心引入的依赖会不会和其他依赖冲突,会不会因此导致服务无法启动,会不会引入漏洞,这不就从根本上简化了操作吗?

      三、子工程调试

        创建好了父工程,并不代表它一定是可用的。上文已经说过,在整理依赖组件时,所有的版本都是能取最新取最新,但有可能很多最新版的依赖相互之间并不兼容,所以还需要进行一定的调试。于是我先对父工程进行mvn install,再引用父工程创建了一个子工程,如下:

        主要是编写上图中的pom文件,注意要引用父工程,并把所有的依赖包都转过来。有条件的还可以再写个增删改查功能,如下:

<!--CommonMapper.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leixi.leixihub.dao.CommonMapper">

    <select id="getDataBySql" resultType="java.util.Map">
        ${sql}
    </select>

    <update id="updateDataBySql">
        ${sql}
    </update>
</mapper>

//这里是 CommonMapper.java

package com.leixi.leixihub.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;

/**
 *
 * @author leixiyueqi
 * @since 2024/8/5 19:39
 */
@Mapper
public interface CommonMapper extends BaseMapper {
    List<Map<String, Object>> getDataBySql(@Param("sql") String sql);
    
    void updateDataBySql(@Param("sql") String sql);
}

// 这里是Controller方法

    @GetMapping("/getDataBySql")
    public Object getDataBySql(@RequestParam(value = "sql") String sql) {
        return commonMapper.getDataBySql(sql);
    }

      四、接口测试

        有了这些代码和依赖,只要项目能成功启动,增删改查能顺利执行,就说明这个架子初步搭成了。

      五、依赖包冲突解决和版本调整

        虽说项目可以起来,但这并不代表这个父工程是安全可用的,主要原因有两个:

                 1)引用的依赖包也不一定是安全,可能存在漏洞。

                 2)各依赖包之间可能存在依赖冲突的问题。

        咱们基于以上两点,对这份依赖文件进行一次复查,逐个解决和排查问题。

        1、对于存在漏洞,或者依赖的资源有漏洞的包,需要更换其版本,或者更换其依赖的子包的版本,或者替换另一个jar包。对于这些有问题的包,Idea的pom文件里都会有较明显的提示,一般标黄底的都是有些毛病的,如下图:

         2、检查和处理依赖冲突,点击Idea2023右上角的图标,可以查看当前有冲突的依赖信息:

        如下图,minio-8.5.5和jetcache-starter-redis-2.7.5都依赖checker-qual包,但是使用的版本不一样:

        这种情况下,有两种解决方案,

        1) 降低jetcache-starter-redis的版本号,或者提升minio的版本号,让它们依赖的checker-qual变得一样,可以在https://mvnrepository.com/中查询各包依赖的版本号,如下:

        2)排除minio中低版本的checker-qual依赖,这需要更改pom.xml里的配置,如下:

      六、回写父工程

        根据上面的方法把依赖信息调整之后,一定要记得把相关变化都更新到父工程leixi-hub-parent里,避免每个子类都要进行类似的配置和调整。测试证明,只要父工程配好了<exclusions>,子工程里不用做这些配置,也不会显示冲突项。

      SpringBoot2转3的方法

        除了上文中说到了升级相关环境、依赖的版本,在对实际工程进行升级时,尤其是对以前的SpringBoot2.X升级到3.X时,还需要注意以下方面:

      1. 升级 JDK 17

        Spring Boot 3.0 需要 Java 17 作为最低版本。如果当前使用的是 Java 8 或 Java 11,则需要在 Spring Boot 迁移之前升级 JDK。

      2. 升级到 Spring Boot 3

        查看项目及其依赖项的状态后,要升级到 Spring Boot 3.0 的最新维护版本。对于不需要父工程的项目,可以这么写:

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>3.2.0</version>
</parent>

      3. 配置属性迁移

        在 Spring Boot 3.0 中,一些配置属性被重命名/删除,开发人员需要相应地更新其 application.properties/application.yml为了快速实现这一点,Spring Boot 提供了一个 spring-boot-properties-migrator 模块。咱可以通过将以下内容添加到 Maven pom.xml 来添加迁移器:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
  <scope>runtime</scope>
</dependency>

      4. 升级到 Jakarta EE

        由于 Java EE 已更改为 Jakarta EE,Spring Boot 3.x 的所有依赖项 API 也从 Java EE 升级为 Jakarta EE。代码中需要将所有 javax 的 imports 都替换为 jakarta。具体如下:

javax.persistence.*   -> jakarta.persistence.*
javax.validation.*    -> jakarta.validation.*
javax.servlet.*       -> jakarta.servlet.*
javax.annotation.*    -> jakarta.annotation.*
javax.transaction.*   -> jakarta.transaction.*

      5. 调整ConstructorBinding注解

        @ConstructorBinding@ConfigurationProperties 类的类型级别不再需要,应将其删除。

当一个类或记录有多个构造函数时,它仍然可以在构造函数上使用,以指示应使用哪一个构造函数进行属性绑定。

      6. 尾部斜杠URL匹配更改

        从 Spring Framework 6.0 开始,尾部斜杠匹配配置选项已为 deprecated,其默认值设置为 false。这意味着以前,以下控制器将匹配GET /healthGET /health/

@RestController
public class HealthController {

  @GetMapping("/health")
  public String health() {
    return "Application is Working";
  }

}

      7. RestTemplate 调整

        Spring Framework 6.0 中已删除对 Apache HttpClient 的支持,现在由 org.apache.httpcomponents.client5:httpclient5 取代。如果 HTTP 客户端行为存在问题,则 RestTemplate 可能会回退到 JDK 客户端。org.apache.httpcomponents:httpclient 可以由其他依赖项传递传递,因此在应用程序可能依赖此依赖项而不声明它。

下面是迁移后的RestTemplate示例:


import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.util.Timeout;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 *
 * @author leixiyueqi
 * @since 2024/8/5 21:39
 */
@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return buildTemplate(10000, 10000,3);
    }


    private RestTemplate buildTemplate(long requestTimeout, long connectTimeout, int retryTimes) {
        final SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactoryBuilder.create()
                .build();
        final PoolingHttpClientConnectionManager manager = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .build();

        final CloseableHttpClient closeableHttpClient = HttpClients.custom()
                .setDefaultRequestConfig(RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(requestTimeout))
                        .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)).build())
                //.setRetryStrategy(new DefaultHttpRequestRetryStrategy(retryTimes, NEG_ONE_SECOND))
                .setConnectionManager(manager)
                .build();

        final HttpComponentsClientHttpRequestFactory componentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        componentsClientHttpRequestFactory.setHttpClient(closeableHttpClient);

        final RestTemplate restTemplate = new RestTemplate(componentsClientHttpRequestFactory);
        return  restTemplate;
    }
}

      8. 升级 Spring Security

        Spring Boot 3.0 已升级到 Spring Security 6.0。因此,WebSecurityConfigurerAdapter 已被弃用。 Spring鼓励用户转向基于组件的安全配置。可使用 Spring Security lambda DSL 和方法 HttpSecurity#authorizeHttpRequests 来定义自己的授权规则。

        下面是使用 WebSecurityConfigurerAdapter 的示例配置,它通过 HTTP Basic 保护所有端点:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    }

}

         展望未来,推荐的方法是注册一个 SecurityFilterChain bean:

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }

}

     9. Spring Kafka 模板升级

        KafkaTemplate 方法现在返回 CompleteableFuture 而不是 ListenableFuture,后者已被弃用。Spring Boot 2.x 中带有 ListenableFuture 的 Kafka 模板:

private RoutingKafkaTemplate routingKafkaTemplate;

public void send(){
    ListenableFuture<SendResult<Object,Object>> future = routingKafkaTemplate.send("Message","topic");

    future.addCallback(new ListenableFutureCallback<>() {
        @Override
        public void onFailure(Throwable ex) {
            log.error(ex);
        }

        @Override
        public void onSuccess(SendResult<Object, Object> result) {
            log.info("success");
        }
    });
}

        Spring Boot 3.x 中带有 CompletableFuture 的 Kafka 模板:

private RoutingKafkaTemplate routingKafkaTemplate;

public void send() {
    CompletableFuture<SendResult<Object, Object>> future = routingKafkaTemplate.send("Message", "topic");
    future.thenAccept(log::info)
            .exceptionally(exception -> {
                log.error(exception);
                return null;
            });
}

      10. Spring Doc OpenAPI 升级

        springdoc-openapi用于为Spring Boot 项目自动生成 API 文档。 springdoc-openapi的工作原理是在运行时检查应用程序,以根据 spring 配置、类结构和各种注释推断 API 语义。对于 spring-boot 3 支持,请确保使用 springdoc-openapi v2。对于 WebMVC 项目,需要在 pom.xml. 文件中包含以下依赖项。

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

        对于 WebFlux 项目,您需要在 pom.xml. 文件中包含以下依赖项。

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.3.0</version>
</dependency>

参考资料

        以下是相关参考资料,感谢大佬们的倾情整理。

        参考0jdk8升级JDK17避坑指南

        参考1:重磅!Spring Boot 2.7 正式发布

        参考2:hutool-希望Hutool能支持下JDK8~JDK17的所有版本

        参考3:一文详解|从JDK8飞升到JDK17,再到未来的JDK21

        参考4:从 Java 8 升级到 Java 17 踩坑全过程,建议收藏!

        参考5:老卫waylau-JDK

        参考6:java - Spring Boot 2.x 到 3.2 的全面升级指南

     后记

        抛开过程中的困难和曲折不说,这是一次酣畅淋漓,难得且难忘的一次机会,一个中小型公司终其一生,能有几次这么升级框架的机会?又有多少程序员能有这样的经历?讲真,我是很感激老大能给我这个机会的。通过这次升级,我对于SpringBoot项目的整体架构,父子工程关系等都有了更深的认识,对依赖整理,漏洞排除也有了相关的积累。苦点累点没什么,获得的成就感却是满满的,将来,公司所有的项目,都将在整理的工程基础上进行建设,我不就是名副其实的奠基人了吗?(可把我给牛批坏了!)

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值