Spring Boot 2.2升级到3.x实践:从依赖迁移到监控优化
1. 升级前的需求分析与整体迁移策略1.1 为什么从 2.2 起步而不是直接跳 3.x先说背景。我维护的这套系统是 2020 年上线的老项目当时 Spring Boot 2.2.x 正是主流版本搭配 Spring Cloud Hoxton、JDK 8跑了两年多一直挺稳。真正让我下定决心做升级的不是新特性有多吸引人而是三点现实压力一是 2.2.x 的维护期早已结束CVE 漏洞只能靠自测修复兜底二是团队今年新招的同事简历里清一色写着熟悉 Spring Boot 3.x老版本对新人来说学习成本反而更高三是下游合作方开始推 OpenAPI 3.0 的接口规范而我用的 springfox 2.9.2 停更太久连 swagger 注解都还在用过时的Api那套生成的文档和新规范对不上。这里要解释一个很多人会踩的误区升级未必是“版本越高越好”的线性过程而是一次需要统筹兼容性、团队技术栈、第三方依赖版本的整体工程。就拿 Spring Boot 2.7 这个版本来说它是 2.x 系列的最后一个功能版本官方明确把它定位成“迁移到 3.x 的桥梁版本”提供了配置属性迁移工具、跨版本弃用警告等过渡机制。如果你现在是 2.2 或 2.3 的老项目我强烈建议先升级到 2.7验证业务功能回归和性能指标正常之后再评估要不要进入 3.x。一步跨两步走排查问题时才能把“Spring Boot 自身变化”和“其他中间件不兼容”两件事分开不然混在一起会非常痛苦。1.2 版本路径选择和升级原则我最终定的路线是2.2.x → 2.7.x → 3.x。中间没有走 2.3、2.4、2.5、2.6 逐版本串行原因很简单Spring Boot 的升级文档里明确支持跨小版本升级重点变化会在 release notes 里集中列出逐版本串行反而会消耗大量回归测试时间。但“跳版本”不等于“不看中间版本的变化”这个后面讲配置迁移时会详细说。在动手之前我定了三条硬性原则所有团队改动都要围绕这三条来执行任何第三方依赖升级之前先查它所依赖的 Spring Boot 版本区间。很多兼容性问题不是 Spring Boot 本身导致的而是某个二方包或开源库绑定了旧版的 spring-core。升级期间保持pom.xml的格式规范化父 POM 版本、依赖管理、属性变量抽离清晰避免升级脚本/插件无法正确解析。每次版本升级完成之后都留出至少两天的回归窗口重点跑接口自动化用例、健康检查、内存监控而不是急着提交上线。这套原则在我后来的 3.x 升级中帮了大忙很多同事一次性迈两个大版本结果被一堆“过期-废弃-替换”类 API 变更淹没压根没法定位真问题。2. 2.2 到 2.7 的关键变更与实操迁移2.1 配置属性变化解读Spring Boot 2.4 起配置文件的加载方式有一个比较大的调整默认不再建议用spring.profiles来指定多环境配置文件而是引入了spring.config.activate.on-profile和spring.config.import这两个思路。后者尤其关键因为原先团队里常见的是application-dev.yml、application-prod.yml这种文件名区分方式在 2.4 之后可以通过spring.config.import: optional:classpath:/application-extra.yml把额外的配置文件动态导入这对配置中心类场景非常有用。实际操作的时候我重点关注了几个被废弃的配置项server.servlet.session.timeout的默认单位调整原来1可能被理解为 1 秒之后强制要求带单位比如1h、30m。spring.datasource.initialization-mode的取值变化在 2.5 以前默认是embedded之后调整成需要显式配置always才能对非嵌入式数据库执行 schema 脚本。spring.jpa.hibernate.ddl-auto的行为在部分场景下会有告警建议通过日志观察是否有HibernateJpaConfiguration的提示。这里建议大家用官方提供的配置属性迁移工具spring-boot-properties-migrator加入依赖之后启动应用它会把旧属性名自动映射到新属性名并在日志中打印提示。实测下来这个工具对老项目找“暗坑”特别有用有些属性你根本不会想到它改过名。2.2 默认端点与健康检查调整从 2.2 到 2.7Spring Boot Actuator 的变化也比较明显。Actuator 在 2.x 时代逐步确立了“暴露端点”的配置方式但 2.2 到 2.7 之间management.endpoints.web.exposure.include的默认值没有太大变化真正要注意的是健康检查组和 Readiness/Liveness 状态机的引入。在 Spring Boot 2.2 里/actuator/health返回的是一个简单的{status:UP}到 2.7 之后健康检查可以配置成组例如management.endpoint.health.group.readiness.includereadinessState,db和management.endpoint.health.group.liveness.includelivenessState这对 Kubernetes 的存活探针和就绪探针非常关键。如果你的基础设施在往容器化方向走这步升级中把健康检查组配置好比单纯升级版本本身更有价值。另外要注意的是/actuator/conditions、/actuator/mappings、/actuator/beans这些端点在生产环境如果通过 Web 暴露一直都有信息泄露风险。我用management.endpoints.web.exposure.includehealth,info做了最小化暴露然后把health的显示细节设置为never避免数据库类型、版本等内部信息被探测到。在 Spring Boot 2.7 中健康组支持自定义展示细节生产环境建议保留默认或隐藏细节这是很多生产事故的前置诱因。2.3 Spring Security 与 OAuth2 的迁移如果项目里用了 Spring Security2.2 到 2.7 的升级要留意几个 API 变化。很典型的是基于内存的用户的写法2.2 时代常见的是这样Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager new InMemoryUserDetailsManager(); manager.createUser(User.withDefaultPasswordEncoder() .username(admin) .password(secret) .roles(ADMIN) .build()); return manager; }在 2.7 中User.withDefaultPasswordEncoder()仍然能用但会打印弃用警告。更推荐的方式是把密码用PasswordEncoder实例显式编码例如在配置类中注入BCryptPasswordEncoder再构建UserDetails。实际上这也符合安全最佳实践——明文密码还是演示代码里才有的写法啊。还有 WebSecurityConfigurerAdapter 的废弃问题。它是 Spring Security 5.4 开始标记废弃的到 Spring Security 5.7 之后彻底移除。如果你的项目大量用了继承WebSecurityConfigurerAdapter的方式做配置升级到 2.7 时建议同步改成 SecurityFilterChain 的 Bean 声明方式否则到 3.x 阶段几乎是寸步难行。2.4 第三方依赖兼容性排查与升级我给项目做 2.7 升级时额外的依赖大概涉及这些Spring Cloud Hoxton 对应的是 Spring Boot 2.2必须先升到 Spring Cloud 2021.0.xJubilee或 2022.0.xKilburn才能配合 Spring Boot 2.7。这步没有捷径只能按官方Spring Cloud Release Train的配套关系表逐项核对。还有 MyBatis、Druid、Redisson 这类常见中间件分别要检查各自的 starter 版本。例如 druid-spring-boot-starter 在 1.2.6 之后的版本对 Spring Boot 2.6 做了适配如果你的项目还在用 1.1.x启动时会出现NoSuchBeanDefinitionException或动态数据源失效的诡异问题。这种问题出错位置不在业务代码而在于自动配置类没有生效排查起来极其耗时——我建议在升级时直接查一下各中间件官方仓库的 README 或 release notes别只看着它能编译过就以为万事大吉。3. 跨入 3.xJDK 17 与 Jakarta EE 的全面适配3.1 升级到 JDK 17 的前置准备Spring Boot 3.x 的最低要求是 JDK 17这是一个硬性门槛。如果你的项目还在用 JDK 8那么需要一个过渡计划而不是简单改java.version属性。JDK 8 到 17 之间跨越了几个重要变化模块化系统JPMS在 JDK 9 引入、垃圾回收器的默认值在 JDK 9 中变成了 G1、JDK 17 中强封装了sun.misc等内部 API。具体到项目构建上我建议分几步走先本地安装 JDK 17通过export JAVA_HOME切换环境但不要直接改 CI 镜像的基础镜像。用mvn clean test全量跑一遍重点看编译错误和测试报错提示例如java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException这类问题属于 JDK 11 之后移除了 Java EE 模块的典型症状。如果没有使用 JPMS 做模块化拆分大多数情况下不需要显式添加--add-opens但如果用到反射提前检查相关库的版本。这里要给一个比较务实的建议不要先升级 JDK 再升级 Spring Boot而是先用 JDK 8 编译 Spring Boot 3.x 试试看当然大概率会失败。最顺利的做法是 Spring Boot 2.7 JDK 17 先作为第一候选组合验证无碍之后切换到 Spring Boot 3.x JDK 17。这个过渡序列能让编译错误和运行错误分开显形。3.2 javax.* 到 jakarta.* 命名空间迁移Spring Boot 3.x 最重要的基础变化就是底层从 Java EE 8javax迁移到了 Jakarta EE 9/10jakarta。这意味着所有涉及 Servlet、Validation、Persistence、Annotation 等 API 的包名都要改。不夸张地说javax.servlet要改成jakarta.servletjavax.validation改成jakarta.validationjavax.persistence改成jakarta.persistence连javax.annotation.PostConstruct也要一起迁移到jakarta.annotation。实际操作中最常见的方式是全局搜索替换但有几个例外要谨慎处理javax.xml.bind相关包JDK 11 之后已经移出默认模块一般通过引入jakarta.xml.bind-api来替代。javax.transaction在部分框架中仍然以传递依赖形式存在全局替换前先确认第三方包没有显式依赖它。自定义注解中的Target({ElementType.METHOD})不受命名空间影响但要检查注解定义时是否引用了javax.annotation或jakarta.annotation的类。我在执行时用的是 IDE 的全局替换加mvn -DskipTests compile快速验证反复了四五轮才把所有遗漏的javax.*清干净。有条件的话可以写一个简单的 Shell 脚本扫描*.java文件中是否还有import javax.防止事后在不经意的地方踩雷。3.3 Spring Security 6 与 OAuth2 的体系升级如果 2.7 阶段还没把WebSecurityConfigurerAdapter迁移到SecurityFilterChain那 3.x 的 Spring Security 6 会把这个问题放大十倍。我 2.7 迁移时提前做了储备所以到 3.x 阶段相对平滑。但 Spring Security 6 还有其他变化比如HttpSecurity的链式调用、方法安全配置EnableGlobalMethodSecurity废弃改用EnableMethodSecurity以及 CSRF 默认策略的变化。这里要重点提一下authorizeRequests()改成authorizeHttpRequests()这个点http .authorizeRequests() .antMatchers(/public/**).permitAll() .anyRequest().authenticated();在 Spring Security 6 中这种方法已经被移除需要用authorizeHttpRequests()和requestMatchers()http .authorizeHttpRequests(authorize - authorize .requestMatchers(/public/**).permitAll() .anyRequest().authenticated() );很多老项目在升级后启动直接报错提示找不到antMatchers方法根本原因就是新旧 API 的混用。这不是一个简单改名而是授权模型从AntPathRequestMatcher向MvcRequestMatcher的转变对路径匹配规则的理解也要跟着调整。3.4 配置迁移与自动配置代码调整Spring Boot 3.x 中自动配置类的位置和加载方式有一些细微变化。如果你之前通过META-INF/spring.factories声明了自定义自动配置那么 3.x 推荐改用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。这是一个破坏性变化因为spring.factories机制在 2.7 时已经被标记废弃。具体操作是在src/main/resources/META-INF/spring/目录下新建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件写入你所有自动配置类的全限定类名每行一个。同时删除spring.factories中的自动配置声明。如果有其他类型的spring.factories条目如ApplicationContextInitializer、EnvironmentPostProcessor仍然保留在spring.factories中这个要区分清楚。除此之外配置文件中的spring.flyway、spring.redis等命名空间在 3.x 中也有调整建议直接利用spring-boot-properties-migrator检查一遍比对着文档翻要高效很多。4. 工具链升级Maven 构建与依赖管理4.1 从 Maven Wrapper 到依赖版本统一管理项目原来用的是 Maven 3.6 和手动维护的dependencyManagement在 3.x 升级中出现了几个问题。首先是 Maven 3.6 对 Maven Compiler Plugin 3.11.0 的处理存在兼容问题建议直接把 Maven 版本升到 3.8.8 或 3.9.x。其次是 Spring Boot 3.x 的父 POM 引入的插件版本偏高如果你的 Maven 版本过旧构建时会报“Unsupported major.minor version”类错误。我同时做了两个行为习惯上的调整一是把所有的版本号集中管理在properties中例如properties java.version17/java.version spring-boot.version3.2.5/spring-boot.version spring-cloud.version2023.0.1/spring-cloud.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties二是利用spring-boot-dependenciesBOM 来统一管理依赖版本不直接在子模块中写死版本号。这样做的好处是Spring Boot 升级时依赖版本会自动跟着变减少冲突。4.2 构建插件与镜像化配置Spring Boot 3.x 的 Maven 插件在repackage目标上做了增强特别是对 layered jar 的支持更加完善。如果你有构建 Docker 镜像的需求建议优先考虑用spring-boot:build-image配合 Cloud Native Buildpacks 的方式而不是手动写 Dockerfile。这样会自动选择合适的 JDK 基础镜像也能把 jar 包按 layers 拆分提升镜像构建缓存利用率。不过在实际项目中可能很多公司的镜像仓库有定制的安全扫描和网络策略无法直连外部 Buildpacks 镜像源。这种情况下还是可以用传统 Dockerfile但要注意在构建阶段使用 Eclipse Temurin JDK 17 的镜像来编译在运行阶段用 JRE 镜像如eclipse-temurin:17-jre或带有-slim标签的精简镜像可以显著降低镜像体积。4.3 依赖树分析实战方法升级过程中我最常执行的命令是mvn dependency:tree -Dverbose当出现莫名其妙的NoClassDefFoundError或ClassNotFoundException时先用这个命令查看依赖树确认某个 jar 的当前版本和来源路径。例如项目里同时存在springfox-swagger2和springdoc-openapi-starter-webmvc-ui时由于两个库都提供了springfox.documentation和io.swagger.v3.oas相关类很容易出现接口文档空白或启动失败。这种情况要果断移除 springfox全面改用 springdoc。还有一个小技巧在pom.xml里临时用exclusions排除可疑的冲突依赖启动应用看是否恢复正常用“二分法”缩小冲突范围。这个方法在 Spring Boot 3.x 升级的场景下尤其好用因为第三方库对 Jakarta 的适配程度参差不齐依赖冲突往往表现为运行期异常而不是编译期错误。5. 热词关联下的功能扩展Actuator 监控与 OpenAPI 文档迁移5.1 Actuator 端点在 3.x 中的最佳实践升级到 3.x 之后micrometer spring boot actuator这个组合成了默认监控方案。Spring Boot 3 依赖 Micrometer 1.11支持将指标暴露给 Prometheus 等监控系统。我建议在配置中显式开启management: endpoints: web: exposure: include: health,info,prometheus,metrics endpoint: health: probes: enabled: true metrics: tags: application: ${spring.application.name}注意management.endpoint.health.probes.enabledtrue这个配置它允许把 liveness 和 readiness 的探针状态单独暴露对容器编排平台非常重要。配合management.metrics.tags.application你在 Prometheus 里就能按应用名区分不同服务的指标而不是光看一个 JVM 的 heap 使用量。Actuator 漏洞是老生常谈的话题但老版本中的风险确实更高。比如早期版本如果配置不当/actuator/env、/actuator/heapdump可能暴露配置中心和内存信息。在 3.x 中这些端点默认关闭但如果你为了排查线上问题临时打开过一定要记得改回来。生产环境建议用 Spring Security 对/actuator/**做 IP 白名单或内网访问限制别只依赖配置文件的暴露开关。5.2 Springfox 迁移至 Springdoc老项目的 swagger 文档迁移是 3.x 升级中的重灾区。Springfox 2.9.2 停留在 2018 年对 Spring MVC 6 的路径匹配、jakarta.servlet命名空间完全不兼容升级后大概率出现接口列表空白、文档页打不开、甚至应用启动失败。我的建议是直接替换成springdoc-openapi-starter-webmvc-ui它是目前社区活跃度最高的 OpenAPI 实现。具体改动大概分三步删除 springfox 依赖引入dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.5.0/version /dependency把Api、ApiOperation、ApiParam等注解替换为Tag、Operation、Parameter。在配置类中重新定义 OpenAPI BeanBean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(API Documentation) .version(v1.0.0) .description(Spring Boot 3.x OpenAPI 3.0 Demo)); }如果项目里自定义了大量的 swagger 注解逐一手工替换很繁琐。可以先用 IDE 的全局替换处理常见注解再逐个看编译报错。更省力的方式是在新代码中坚持用 springdoc 注解老接口如果已经不维护了可以先不做替换但要注意注册的路径冲突。5.3 JSON、目录规范与 Spring Boot 技能沉淀升级过程中最容易被忽视的其实是编码规范和目录结构调整。热词里反复出现“spring boot目录规范”说明不少团队做项目时对包结构没有沉淀出统一约定。我这次升级也顺便把包结构做了调整从原来的“按技术类型分包”controller、service、dao改成了“按业务域分包”user、order、notification配合OpenAPI的标签组织和 Actuator 的metrics.tags分区整体可维护性提升明显。另外Spring Boot 3.x 中spring-boot-starter-json已经内置了 Jackson 的处理JSON 序列化上需要留意的是LocalDateTime的默认格式问题。老项目往往自定义了ObjectMapper的JavaTimeModule但升级后如果引入了多个ObjectMapperBean会出现配置不生效的情况。这时候要在application.yml中显式配置spring.jackson前缀下的属性或者自定义Jackson2ObjectMapperBuilderCustomizer来统一处理。5.4 面试高频问题与团队技能同步随着 Spring Boot 3.x 成为主流团队里新人对“Spring Boot 自动配置原理”“Actuator 监控指标”“Spring Security 过滤器链”这类面试题越来越熟悉但对老版本到新版本的迁移经验反而稀缺。我在升级完成后专门整理了一份内部技术文档把常见问题汇总成了“升级三问”你的项目当前处于哪个 Spring Boot 版本是否还在官方支持周期内你的第三方依赖是否兼容 Jakarta 命名空间是否适配 JDK 17你的监控、日志、构建链路是否跟上了新版 Actuator 和 Maven 插件的变化这些问题不仅是面试知识点更是后续维护时最容易踩坑的地方。6. 常见问题与问题排查实录6.1 启动失败路径匹配策略导致 404Spring Boot 3.x 中默认禁用了后缀模式匹配spring.mvc.pathmatch.matching-strategy已废弃如果代码中有基于*.do或*.json这种后缀的 URL 映射会在启动时不报错但实际请求全部 404。典型的排查方式是打开 debug 日志查看RequestMappingHandlerMapping初始化的路径列表确认是否只有部分 URL 被注册。这里的关键点是Spring MVC 的路径匹配策略从AntPathMatcher切换到了PathPatternParser对**通配符的语义有所不同特别是匹配根路径和公共前缀时差别明显。如果系统中大量使用自定义拦截器对路径做通配拦截建议先核对符合新规则的写法例如用/api/**而不是/api/*。6.2 数据库访问异常Druid JDK 17 的兼容性问题我遇到的一个比较典型的故障是升级到 JDK 17 之后应用启动一切正常但首次访问数据库接口时直接抛出Error creating bean with name dataSource看堆栈是 Druid 在初始化DruidDataSource时反射获取字段失败。查了之后发现 Druid 1.2.6 以下版本对 JDK 17 的模块化访问限制处理不完善需要升级到 1.2.15 或改用 HikariCP。这里有个排查思路值得分享遇到InaccessibleObjectException时先判断是第三方库自身代码的问题还是需要手动添加--add-opensJVM 参数。Spring Boot 3.x 官方文档建议尽量升级依赖而不是无脑堆--add-opens因为参数加多了既影响性能又掩盖问题根源。6.3 内存与 GC 调优的变化Spring Boot 3.x JDK 17 环境下默认 G1 垃圾回收器的行为和老 JDK 8 的 PS 收集器差别较大。我遇到的直接表现是接口响应偶尔出现毛刺监控里 G1 的 Young GC 频率偏高。建议先观察jstat -gcutil的输出判断是对象分配速率过快还是堆内存配置不足不要盲目调整-Xmx。如果服务是容器化部署JDK 17 已经支持根据容器内存自动配置堆大小但默认的 MaxRAMPercentage 可能偏保守一般建议显式设置-XX:MaxRAMPercentage75.0配合-XX:InitialRAMPercentage50.0既能保证堆内有足够空间又不会把宿主机内存吃满。6.4 API 文档空白问题升级到 Spring Boot 3.x 后访问/swagger-ui.html能打开页面但接口列表完全空白。这个问题通常是 Springfox 与 Springdoc 共存导致的类冲突或路径映射冲突。我之前的排查方法是先移除 springfox 依赖清理 target 目录重新编译再看springdoc是否正常工作。还有少数情况是spring.mvc.pathmatch.matching-strategy被显式设置成了ant_path_matcher导致 Springdoc 无法正确扫描路径把它删掉或改成默认值就能解决。6.5 埋点数据缺失问题如果从 2.2 直接跨到 3.x并且之前依赖了 Dropwizard Metrics 或自定义的PublicMetrics接口升级之后这些扩展点大多被移除或替换成 Micrometer 的MeterBinder。症状表现为/actuator/metrics看不到业务自定义指标。改造方式也不难把原先实现PublicMetrics的类改成实现MeterBinder在bindTo(MeterRegistry registry)方法里注册 gauge、counter 等指标即可。这个模块当时花了不少时间因为老代码里用了GaugeService和CounterService这两个 Spring Boot 1.x 遗留组件在 2.2 还能勉强用到 3.x 直接没有对应类。好在 Micrometer 的 API 设计比较直观迁移逻辑本身不算难主要是把“手动上报”的思路转成“注册绑定”的思路。7. 升级复盘与个人体会这次从 2.2 到 2.7 再到 3.x 的升级前后总共花了三周左右的时间。第一周做依赖梳理和环境搭建第二周处理代码兼容性改动第三周专项测试和调优。实际上代码改动的量并不算特别大更大的成本在回归测试和线上兼容性验证上。如果你也想做类似升级我建议把下面几点作为优先级最高的行动项先做依赖分析和版本清单搞清楚每一个中间件的兼容边界再动手。最怕的是升级到一半发现某个老组件根本没有替代方案。充分利用 2.7 作为桥接版本把配置属性、Actuator、Spring Security 的废弃警告提前消化掉。这样 3.x 升级会平滑很多。不要忽略 JDK 17 本身的变化把编译环境、运行环境、CI 构建环境三者统一起来避免“本地能跑、线上启动失败”的窘境。监控链路Metrics、Health、日志要在升级过程中同步改造而不是升级完再处理否则无法判断新版本的内存、GC、接口性能是否正常。“Spring Boot 升级”看上去是个技术活本质上是“依赖管理”“兼容性验证”“架构演进”三件事的综合体。每个项目的基础设施、业务复杂度、团队水平都不一样所以我上面这些步骤未必能直接照搬但思路是可以复用的先理清现状再选稳定路径最后用监控数据验证每一步的结果。如果你也在做类似的版本升级建议每走一步都随手记录一下踩过坑之后再看这些日志你会发现排查速度比想象中快得多。

相关新闻

最新新闻

日新闻

周新闻

月新闻