前言:为什么现在必须升级到Spring Boot 3.2?

如果你还在使用Spring Boot 2.7.x,你可能面临这些问题:

  • 🚫 2024年8月后Spring Boot 2.7将停止维护(包括安全更新)

  • 🚫 无法使用JDK 21的虚拟线程等新特性

  • 🚫 错过Spring Framework 6的性能提升(响应速度提升40%)

  • 🚫 无法集成最新AI、云原生技术栈

但升级过程确实充满挑战:包名从javaxjakarta、配置项大规模变更、第三方依赖兼容性问题...

本文将带你用最小的代价完成从2.7到3.2的平稳升级,每个步骤都包含可执行的代码和验证方法。

升级前准备:风险评估与检查清单

1.1 项目健康度检查

在开始升级前,先运行以下检查:

# 1. 检查当前依赖健康状况
mvn dependency:tree | grep -E "(javax|log4j|logback|spring-security)" > deps-audit.txt

# 2. 运行完整测试套件
mvn clean test -DskipTests=false

# 3. 生成API变更报告
mvn versions:display-plugin-updates
mvn versions:display-dependency-updates

1.2 必须完成的准备工作

✅ 备份完整代码:确保有可回退的版本

git checkout -b spring-boot-3-upgrade
git add .
git commit -m "Backup before Spring Boot 3.2 upgrade"

✅ 记录关键配置:特别关注以下文件:

  • application.properties / application.yml

  • pom.xml 中的依赖版本

  • 自定义的@Configuration

  • 过滤器、拦截器配置

✅ 建立性能基线

# 记录当前应用启动时间
time mvn spring-boot:run

# 记录关键API响应时间(使用JMeter或简单脚本)

第1步:JDK版本升级(从JDK 8/11到JDK 17+)

1.1 修改pom.xml中的JDK配置

<!-- 在pom.xml的properties部分修改 -->
<properties>
    <!-- 从JDK 8/11升级到JDK 17 -->
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    
    <!-- 可选:启用预览特性(如需使用虚拟线程) -->
    <maven.compiler.compilerArgs>
        <arg>--enable-preview</arg>
    </maven.compiler.compilerArgs>
</properties>

1.2 处理JDK 17的模块系统问题

如果你的项目使用了反射或动态代理,需要在module-info.java中添加:

module your.application {
    requires spring.core;
    requires spring.context;
    requires spring.web;
    
    // 允许反射访问
    opens com.yourpackage to spring.core, spring.beans, spring.context;
    
    // 如果使用Jackson
    opens com.yourpackage.model to com.fasterxml.jackson.databind;
}

或者,在启动参数中添加(临时方案):

-Dspring.native.remove-yaml-support=false \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED

第2步:Spring Boot父POM版本升级

2.1 修改父依赖版本

<!-- 从2.7.x升级到3.2.4 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!-- 从原来的2.7.x改为 -->
    <version>3.2.4</version>
    <relativePath/>
</parent>

<!-- 或者使用dependencyManagement方式 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2.2 处理废弃的依赖

Spring Boot 3.2移除了以下starter,需要手动添加:

<!-- 如果使用了以下starter,需要单独引入 -->
<dependencies>
    <!-- 1. spring-boot-starter-log4j2 (如果使用Log4j2) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        <exclusions>
            <exclusion>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    
    <!-- 2. 如果使用了spring-boot-starter-tomcat(现在内嵌在web中) -->
    <!-- 无需单独声明 -->
</dependencies>

第3步:javax到jakarta的包名迁移

3.1 使用IDE的全局替换功能

在IntelliJ IDEA或Eclipse中执行全局替换:

  • 查找javax.persistence → 替换为jakarta.persistence

  • 查找javax.servlet → 替换为jakarta.servlet

  • 查找javax.validation → 替换为jakarta.validation

  • 查找javax.annotation → 替换为jakarta.annotation

3.2 关键变更点检查清单

// 1. JPA实体类
@Entity
@Table(name = "users")
public class User {
    // @Id, @GeneratedValue 等注解现在来自jakarta包
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

// 2. Controller中的校验注解
@RestController
public class UserController {
    @PostMapping("/users")
    public ResponseEntity<User> createUser(
        @Valid @RequestBody UserDto userDto) {  // @Valid现在来自jakarta
        // ...
    }
}

// 3. 过滤器配置
@Component
public class AuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
        HttpServletRequest request,   // jakarta.servlet.http.HttpServletRequest
        HttpServletResponse response,
        FilterChain filterChain) {
        // ...
    }
}

3.3 自动化迁移脚本(可选)

对于大型项目,可以使用OpenRewrite自动迁移:

<!-- 在pom.xml中添加 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.openrewrite.maven</groupId>
            <artifactId>rewrite-maven-plugin</artifactId>
            <version>5.40.0</version>
            <configuration>
                <activeRecipes>
                    <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_2</recipe>
                    <recipe>org.openrewrite.java.migrate.jakarta.JavaxToJakarta</recipe>
                </activeRecipes>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.openrewrite.recipe</groupId>
                    <artifactId>rewrite-spring</artifactId>
                    <version>5.7.0</version>
                </dependency>
                <dependency>
                    <groupId>org.openrewrite.recipe</groupId>
                    <artifactId>rewrite-migrate-java</artifactId>
                    <version>2.13.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

运行迁移:

mvn rewrite:run

第4步:Spring Security 5.x到6.x的迁移

4.1 配置类重大变更

// Spring Security 5.x的配置(需要修改)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic();
    }
}

// Spring Security 6.x的配置(新写法)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .httpBasic(Customizer.withDefaults());
        
        return http.build();
    }
}

4.2 关键API变更

Spring Security 5.x Spring Security 6.x 迁移说明
antMatchers() requestMatchers() 方法名变更
mvcMatchers() requestMatchers() 统一使用新API
.and() Lambda DSL 不再需要链式调用
WebSecurityConfigurerAdapter SecurityFilterChain Bean 类已废弃

4.3 密码编码器变更

// 旧方式(Spring Security 5.x)
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

// 新方式(Spring Security 6.x)- 推荐使用DelegatingPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    String encodingId = "bcrypt";
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(encodingId, new BCryptPasswordEncoder());
    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
    
    return new DelegatingPasswordEncoder(encodingId, encoders);
}

第5步:数据库连接与JPA变更

5.1 数据源配置变更

# Spring Boot 2.7.x的配置(旧)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      connection-timeout: 30000
      maximum-pool-size: 10

# Spring Boot 3.2.x的配置(新)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      connection-timeout: 30000
      maximum-pool-size: 10
    # 新增:SQL初始化模式
    initialization-mode: always  # 改为:spring.sql.init.mode=always

5.2 JPA配置变更

# 重要的配置变更
spring:
  jpa:
    # 旧配置(2.7.x)
    # hibernate:
    #   ddl-auto: update
    #   naming:
    #     physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    
    # 新配置(3.2.x)
    hibernate:
      ddl-auto: update
    # 命名策略配置方式变更
    naming:
      physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
      
    # 新增:延迟加载配置
    properties:
      hibernate:
        enable_lazy_load_no_trans: true
        "[hibernate.format_sql]": true
        
  # SQL初始化配置(从datasource中分离)
  sql:
    init:
      mode: always
      platform: mysql

5.3 数据库驱动版本升级

必须更新数据库驱动版本:

<!-- MySQL驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.2.0</version>  <!-- 必须从8.0.x升级 -->
    <scope>runtime</scope>
</dependency>

<!-- PostgreSQL驱动 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.1</version>  <!-- 必须从42.5.x升级 -->
    <scope>runtime</scope>
</dependency>

第6步:日志框架配置迁移

6.1 Logback配置变更

<!-- logback-spring.xml 重要变更 -->
<configuration>
    <!-- 1. 移除过时的属性 -->
    <!-- 旧:<property name="LOG_PATH" value="./logs" /> -->
    <!-- 新: -->
    <property name="LOG_PATH" value="${LOG_PATH:-./logs}" />
    
    <!-- 2. RollingFileAppender配置变更 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 最大文件大小配置变更 -->
            <maxFileSize>100MB</maxFileSize>  <!-- 单位必须明确 -->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!-- 3. 异步日志配置 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
        <appender-ref ref="FILE" />
        <!-- 新增:避免日志丢失的配置 -->
        <neverBlock>true</neverBlock>
    </appender>
</configuration>

6.2 Log4j2配置(如果使用)

<!-- log4j2-spring.xml 配置示例 -->
<Configuration status="WARN" monitorInterval="30">
    <Properties>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
        <Property name="LOG_PATH">./logs</Property>
    </Properties>
    
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        
        <RollingFile name="File" fileName="${LOG_PATH}/app.log"
                     filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <Logger name="com.yourpackage" level="debug" additivity="false">
            <AppenderRef ref="File"/>
        </Logger>
        
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

第7步:Web配置与Servlet API变更

7.1 WebMvcConfigurer变更

// Spring Boot 2.7.x(旧)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST");
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());
    }
}

// Spring Boot 3.2.x(新)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST")
                .allowCredentials(true);  // 新增:必须显式设置
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**", "/error");
    }
}

7.2 文件上传配置变更

# 文件上传配置变更
spring:
  servlet:
    multipart:
      # 旧配置(2.7.x)
      # max-file-size: 10MB
      # max-request-size: 100MB
      
      # 新配置(3.2.x)
      max-file-size: 10MB
      max-request-size: 100MB
      # 新增:内存阈值配置
      file-size-threshold: 0B
      # 新增:清理临时文件
      location: ${java.io.tmpdir}
      
  # 新增:HTTP配置分离
  web:
    resources:
      cache:
        period: 3600

第8步:Actuator监控端点变更

8.1 端点配置变更

# Spring Boot 2.7.x Actuator配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: always

# Spring Boot 3.2.x Actuator配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus  # 新增prometheus
      base-path: /actuator  # 现在可以自定义
  endpoint:
    health:
      show-details: always
      # 新增:健康检查组配置
      group:
        custom:
          include: diskSpace,ping,db
    # 新增:度量的新配置
    metrics:
      export:
        prometheus:
          enabled: true
          step: 1m

8.2 自定义健康检查

// Spring Boot 3.2.x的健康检查写法
@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    private final DataSource dataSource;
    
    public CustomHealthIndicator(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public Health health() {
        // 检查数据库连接
        try (Connection conn = dataSource.getConnection()) {
            if (conn.isValid(1000)) {
                return Health.up()
                    .withDetail("database", "available")
                    .withDetail("connection_time", "fast")
                    .build();
            }
        } catch (SQLException e) {
            return Health.down(e)
                .withDetail("database", "unavailable")
                .withDetail("error", e.getMessage())
                .build();
        }
        
        return Health.unknown().build();
    }
}

第9步:第三方依赖兼容性处理

9.1 常见依赖升级指南

<!-- 1. MyBatis升级 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>  <!-- 从2.3.x升级 -->
</dependency>

<!-- 2. 如果使用Druid连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-3-starter</artifactId>
    <version>1.2.20</version>  <!-- 注意:必须是3.x的starter -->
</dependency>

<!-- 3. 如果使用Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 注意:需要显式添加commons-pool2 -->
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.12.0</version>
</dependency>

<!-- 4. 如果使用Swagger/SpringDoc -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>  <!-- 替代springfox -->
</dependency>

9.2 必须检查的依赖

运行以下命令检查不兼容的依赖:

# 查找所有javax依赖
mvn dependency:tree | grep javax

# 查找所有可能不兼容的依赖
mvn dependency:tree | grep -E "(servlet|persistence|validation|annotation)" | grep -v jakarta

# 使用Maven Enforcer插件检查
mvn enforcer:enforce -Drules=dependencyConvergence

第10步:测试与验证

10.1 分阶段验证策略

第一阶段:编译验证

# 清理并编译
mvn clean compile

# 检查是否有编译错误
if [ $? -eq 0 ]; then
    echo "✅ 编译通过"
else
    echo "❌ 编译失败,请检查错误信息"
    exit 1
fi

第二阶段:单元测试

# 运行所有单元测试
mvn test -DskipTests=false

# 检查测试通过率
TEST_RESULT=$(mvn test -DskipTests=false 2>&1 | grep "Tests run:")

echo "测试结果:$TEST_RESULT"

第三阶段:集成测试

# 使用Testcontainers运行集成测试
mvn verify -DskipITs=false

# 或者手动启动应用测试
mvn spring-boot:run &
APP_PID=$!

# 等待应用启动
sleep 30

# 测试关键端点
curl -f http://localhost:8080/actuator/health || echo "健康检查失败"
curl -f http://localhost:8080/api/test || echo "API测试失败"

# 停止应用
kill $APP_PID

10.2 性能对比测试

创建性能测试脚本:

#!/bin/bash
# 性能对比测试脚本

echo "=== Spring Boot 2.7.x 性能测试 ==="
# 启动旧版本应用
# 运行压测...

echo "=== Spring Boot 3.2.x 性能测试 ==="
# 启动新版本应用
# 运行同样压测...

# 比较结果
echo "性能提升:"
echo "- 启动时间:减少约30%"
echo "- 内存占用:减少约20%"
echo "- 吞吐量:提升约15%"

10.3 回滚计划

如果升级过程中遇到问题,需要准备回滚方案:

# 1. 代码回滚
git reset --hard HEAD~1

# 2. 数据库回滚(如果执行了迁移脚本)
# 备份恢复...

# 3. 配置文件恢复
cp application-backup.yml application.yml

# 4. 依赖恢复
mvn clean install -U

升级后优化建议

1. 启用新特性

// 1. 启用虚拟线程(需要JDK 21+)
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

// 2. 使用新的RestClient(替代RestTemplate)
@Bean
public RestClient restClient() {
    return RestClient.builder()
            .baseUrl("https://api.example.com")
            .defaultHeader("User-Agent", "SpringBootApp")
            .build();
}

// 3. 响应式编程(如果适用)
@Bean
public RouterFunction<ServerResponse> routes() {
    return RouterFunctions.route()
            .GET("/users", request -> ServerResponse.ok().bodyValue(userService.findAll()))
            .build();
}

2. 监控与告警配置

# 新增:Micrometer监控配置
management:
  metrics:
    export:
      prometheus:
        enabled: true
      datadog:
        enabled: false
      newrelic:
        enabled: false
    distribution:
      percentiles-histogram:
        "[http.server.requests]": true
    tags:
      application: ${spring.application.name}
      environment: ${ENV:local}
      
  tracing:
    sampling:
      probability: 1.0

常见问题与解决方案

Q1:启动时出现ClassNotFoundException: javax.servlet.Filter

原因:没有正确迁移到jakarta
解决

  1. 检查所有import语句

  2. 确保依赖中没有旧的servlet-api

  3. 运行mvn dependency:tree | grep servlet检查

Q2:Hibernate无法启动,报Table not found

原因:DDL自动生成策略变化
解决

spring:
  jpa:
    hibernate:
      ddl-auto: validate  # 先使用validate模式
    properties:
      hibernate:
        "[hibernate.hbm2ddl.auto]": validate

Q3:Spring Security配置不生效

原因:SecurityFilterChain配置错误
解决

@Bean
@Order(1)  // 确保顺序正确
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")  // 明确指定匹配路径
        .authorizeHttpRequests(auth -> auth
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults());
    return http.build();
}

Q4:性能下降或内存泄漏

排查步骤

  1. 使用jcmd <pid> VM.native_memory分析内存

  2. 检查GC日志:-Xlog:gc*:file=gc.log

  3. 使用AsyncProfiler进行性能分析

总结

从Spring Boot 2.7升级到3.2是一个系统性的工程,但收益显著。按照这10个步骤,你可以:

  1. 安全地完成升级,避免生产事故

  2. 充分利用新特性,提升应用性能

  3. 保持技术栈先进性,为未来升级奠定基础

最后建议

  • 在测试环境充分验证至少1周

  • 逐步灰度发布到生产环境

  • 建立监控告警,关注关键指标

升级成功标志
✅ 应用启动时间减少30%以上
✅ 内存占用降低20%以上
✅ 所有测试用例通过
✅ 生产环境运行稳定24小时+

记住:一次成功的升级,不仅是为了使用新版本,更是为了构建更稳定、高效的技术基础。


升级支持:如果在升级过程中遇到问题,可以在评论区留言,我会提供针对性的解决方案。

资源推荐

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐