踩了一堆坑,终于把微服务系统全面升级 JDK17 和 SpringBoot3了

最近正在给自己的开源项目校园博客升级到 JDK17 以及 SpringBoot3,正好记录下升级和踩坑的过程,给大家提供一些解决方案的参考。

先说结论:非常推荐升级JDK17,成本低收益高。至于SpringBoot3.0,迁移成本比较高,坑也会比较多,但如果是新项目的话,还是可以试试的。

PS:项目原来的版本是 JDK8 + SpringBoot2.6。

1. 为什么要升级?

  • JDK17和SpringBoot3也发布了一段时间了,自己对一些新特性也比较感兴趣,尤其是 Native Image 这个玩意。
  • 自己手上刚好有校园博客这个项目,可以用来给进行升级,项目不复杂,但也算五脏俱全,全量升级既可以感受一下变化,也不会太费事。
  • JDK17 是一个长期支持的版本(LTS),现在很多开源应用或者一些组件都在往这上面靠,并且大有一种最低支持 JDK17 的趋势。
  • 自己在公司所接触到的项目也有一部分是使用的JDK17,并且整体有往这方面靠的趋势,新项目都会直接用JDK17。

总的来说就是 兴趣 + 资源 + 趋势。

2. 升级有什么好处?

先来看看 JDK8 -> JDK17 的好处。

  • ZGC垃圾回收器,性能提升
  • 可以使用 var 作为局部变量类型推断标识符
  • 一个文件中可以包含多个public类
  • switch 使用起来更加简洁,可以不用再break了。
  • instanceof 增强
  • 增加不可修改的数据类 record(感觉还是 kotlin 的 data class 好用)
  • Text Blocks文本块

实用性很强,非常舒服。

再看看 SpringBoot3.0 的一些新特性。

  • 更好的支持 Native Image,使用 GraalVM 构建原生镜像,可以提供显著的内存和启动性能改进
  • 升级到 Spring6.0
  • 升级到 Spring Security 6.0
  • ......

好吧,感觉上是不如 JDK17 要更有性价比,如果对 Native Image 兴趣不大的话,建议不要升级SpringBoot3.x,因为升级SpringBoot的成本可要比升级JDK高多了。

3. 升级过程分享

以下的一切内容均基于我已有的项目【校园博客】进行升级和讲解,源码地址:github.com/stick-i/scb...

既然一切都是基于JDK17的,那我们就先升级JDK吧!

3.1. 升级JDK17

3.1.1. 下载安装

安装JDK17,这里我直接在IDEA里面下载安装了,很方便:

为了便于自己以后使用 Native Image,这里我直接下载了 GraalVM。

在IDEA中更新项目SDK和模块SDK:

3.1.2. Maven构建

更新Maven编译配置:

xml 复制代码
<properties>
  <java.version>17</java.version>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

Maven重新打包下:

这一步主要是为了更新下内部组件的 JDK 版本。

3.1.3. 启动服务

测试下有没有其他问题,启动所有微服务项目:

竟然一切正常,也可能与我的项目比较简单有一定的关系,所有服务都成功跑起来了。

3.1.4. 更新Dockerfile

原来使用的基础镜像是 java:8-alpine,更新到了亚马逊的openjdk17版本 amazoncorretto:17-alpine

dockerfile 复制代码
# 设置JAVA版本
FROM amazoncorretto:17-alpine
# 指定存储卷, 任何向/tmp写入的信息都不会记录到容器存储层
VOLUME /tmp
# 拷贝运行JAR包
ARG JAR_FILE
ADD ${JAR_FILE} app.jar
# 设置JVM运行参数,限定内存大小,并设置时区为东八区
ENV JAVA_OPTS="\
-server \
-Xms256m \
-Xmx512m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-Duser.timezone=GMT+08 "
#空参数,方便创建容器时传参
ENV PARAMS=""
# 入口点, 执行JAVA运行命令
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]

3.1.5. 源代码

看起来没什么问题了,先提交上JDK升级的代码,有需要的同学可以查看提交记录:

build(all): 全量升级到jdk17,更新dockerfile和pom文件。 by stick-i · Pull Request #198 · stick-i/scblogs

3.2. 升级SpringBoot3.2

为什么选择直接升级到 SpringBoot3.2 而不是 3.0呢?

主要是我开始升级的时候,SpringBoot已经更新到3.2了,而此时的3.0的生命周期已经过半了,目前也还没有推出3.0以上的LTS版本,这么看来我以后总还是要升级的,倒不如现在一起弄了。

3.2.1. 升级pom依赖

跟SpringBoot相关的依赖还是比较多的,尤其是依赖了SpringCore的三方依赖,肯定也是要统一进行升级的。

截至到我写这篇文章的时间,SpringBoot的最新GA版本为 3.2.1:

我选择相信Spring,直接升级最新版!

对应的SpringCloud版本为2023.0.0

其他主要依赖对应升级的情况:

依赖项 升级前版本 升级后版本 备注
SpringBoot 2.6.11 3.2.1 目前的最新版,要踩坑就踩最新的坑🤡
SpringCloud 2021.0.4 2023.0.0 对应SpringBoot3.2.x
SpringCloudAlibaba 2021.0.4.0 2022.0.0 这个库还没出2023的版本,但是2022版也是基于SpringBoot3.0的,应该不会差太多
Mybatis-Plus 3.5.3.1 3.5.5 注意 :artifactId 从mybatis-plus-boot-starter改成了mybatis-plus-spring-boot3-starter
druid 1.2.11 1.2.20 注意 :artifactId 从druid-spring-boot-starter改成了druid-spring-boot-3-starter

对了,建议顺便升级下Maven。

3.2.2. 解决依赖异常

修改完pom文件之后刷新一下本地依赖包,噢呦,一堆报错:

我看了一下,就两个问题,分别是 mysql-connector-java 和 javax.servlet-api 这两个包的版本没有被指定,所以Maven找不到对应的包。

为什么没有指定呢?之前也没有指定,但是之前没有报错,说明这两个包之前是有被 spring-boot-starter-parent 所管理的,但是现在它不管了。

这得去看看SpringBoot3.0的迁移文档:github.com/spring-proj...

3.2.2.1. MySQL

在网页里搜索关键字 MySQL,这不就来了:

就是说 mysql:mysql-connector-java 这个包的坐标改成了 com.mysql:mysql-connector-j,让我们更新的时候也顺带改一下。这个简单,全局搜索然后改一下就好了。

一改完,版本继承的小图标就出来了,不错不错。

3.2.2.2. javax -> jakarta

然后再搜一下关键字 javax,这不就又来了:

这个就稍微麻烦一点了,不仅Maven坐标从 jakarta.servlet:jakarta.servlet-api 改成了 javax.servlet:javax.servlet-api,而且包名也从 javax.xxx.xxx 改成了 jakarta.xxx.xxx,所有导入了 javax 的包都得改。

先更新下pom文件:

然后再全局搜索 javax 替换下:

我试过了,升级完后唯一出现问题的地方就只有一处,但也很容易修改:

ResponseStatusException 中没有 getStatus() 这个方法了,我使用HttpStatus.valueOf(statusException.getStatusCode().value()) 代替了原来的方法。

做完上面这些后,我的项目已经可以成功编译了,但还不能正常的跑起来。

3.2.3. 配置文件属性迁移

SpringBoot3 更改了一些配置属性,例如:spring.redis.host改为了spring.data.redis.host

这一变更几乎对所有项目都会有影响,要查看所有的变更项,可以在官方文档中进行查找:github.com/spring-proj...

但这太silly了,很显然官方也这么认为,所以给开发者提供了一个简单的迁移方法,引入 spring-boot-properties-migrator ,它会帮你自动检测配置文件中需要修改的地方:

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
  <scope>runtime</scope>
</dependency>

配置文件属性迁移完毕后,记得删除这里添加的 spring-boot-properties-migrator 依赖。

然后运行项目,当然你的项目大概率是运行不起来的,但别着急,看看你的控制台输出,有没有像我这样的输出内容:

上面的异常信息其实分为了两个部分,前面部分是需要进行修改的配置:

The use of configuration keys that have been renamed was found in the environment:

Property source 'bootstrapProperties-default-redis.yaml,DEFAULT_GROUP':

Key: spring.redis.host

Replacement: spring.data.redis.host

Key: spring.redis.password

Replacement: spring.data.redis.password

Key: spring.redis.port

Replacement: spring.data.redis.port

Each configuration key has been temporarily mapped to its replacement for your convenience. To silence this warning, please update your configuration to use the new keys.

它也给出了重命名之后的key,这里直接对着描述把自己的配置文件改改就好了,比较简单。


后面部分是说有一些配置已经被弃用了,但是它也给出了弃用的原因:

The use of configuration keys that are no longer supported was found in the environment:

Property source 'bootstrapProperties-default-springmvc.yaml,DEFAULT_GROUP':

Key: spring.mvc.throw-exception-if-no-handler-found

Reason: DispatcherServlet property is deprecated for removal and should no longer need to be configured

Property source 'bootstrapProperties-default-redis.yaml,DEFAULT_GROUP':

Key: spring.redis.lettuce.pool.max-active

Reason: none

Key: spring.redis.lettuce.pool.max-idle

Reason: none

Key: spring.redis.lettuce.pool.max-wait

Reason: none

Key: spring.redis.lettuce.pool.min-idle

Reason: none

Please refer to the release notes or reference guide for potential alternatives.

好吧,这里其实有点小坑,只有上面第一个Key给了弃用原因,说是DispatcherServlet属性已经被移除了。但是后面几个redis相关的Key都是没有给弃用原因的。

既然这样,那我只能自己去官方文档里找了:github.com/spring-proj...

全局搜索下 lettuce.pool,你别说,还真让我找到了:

明明也没有弃用,就是把redis前面加个data罢了,看来 spring-boot-properties-migrator也偶有瞎说的情况啊。

再次提醒:配置文件属性迁移完毕后,记得删除之前添加的 spring-boot-properties-migrator 依赖。

3.2.4. ES版本兼容

如果你的es客户端版本和es服务端版本一致(均为8.x),可以直接跳过这部分内容。

项目里使用了 spring-boot-starter-data-elasticsearch,升级SpringBoot3.x 之后,这个依赖的版本也·提高了,对应ES的版本是8.x,而我服务器使用的ES版本是7.x,所以有一些不兼容的问题,启动时出现异常:

Caused by: java.lang.RuntimeException: node: http://xxxxxx, status: 200, [es/indices.exists] Missing [X-Elastic-Product] header. Please check that you are connecting to an Elasticsearch instance, and that any networking filters are preserving that header.

本来想通过降低 elasticsearch-rest-client 的版本来解决这个问题,但是降低之后又不能兼容 SpringBoot3 了,于是只能另辟蹊径了。

这个说起来比较麻烦,我在 stackoverflow 上找到一篇帖子,里面有对这个问题的描述,可以参考下:stackoverflow.com/questions/7...

它讲到了两个问题:

  1. 客户端向服务端发送了未知的 Content-Type ,因此其请求被拒绝并返回 406(其实是请求头 compatible-with 不受支持)
  2. 客户端需要验证 response 中是否具有 X-Elastic-Product=Elasticsearch 标头,但服务端并没有返回这个。

问题其实蛮清晰的,但是给出的解决方案让我不太满意,还需要自己重新去构建一个 RestClient,自己读取配置文件然后set进去,又得设置账号密码、又得解析Host的,这我可受不了。

于是经过我的一顿研究之后,我发现了 RestClientBuilderCustomizer 这个类:

翻译:回调接口,可以由希望通过RestClientBuilder进一步定制RestClient的bean实现,同时保留默认的自动配置。

只要用这个玩意,就可以在原有的 RestClient 基础上,进行一些定制化的操作,比如说解决上面那两个问题。于是乎,我就写了下面这一段代码:

java 复制代码
/**
 * Es 兼容性配置,添加响应头,兼容服务端版本
 * <p>
 * 如果客户端与服务端版本一致,可移除此配置。
 *
 * @author 阿杆
 * @version 1.0
 * @date 2024/1/25 22:29
 */
@Component
public class EsCompatibilityConfig implements RestClientBuilderCustomizer {

	@Override
	public void customize(RestClientBuilder builder) {
	}

	@Override
	public void customize(HttpAsyncClientBuilder builder) {
		// 添加响应头,兼容X-Elastic-Product
		HttpResponseInterceptor httpResponseInterceptor =
				(response, context) -> response.addHeader("X-Elastic-Product", "Elasticsearch");
		builder.addInterceptorLast(httpResponseInterceptor);
		// 自定义默认请求头,目的是禁用兼容性请求头 compatible-with
		builder.setDefaultHeaders(List.of(new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())));
	}

}

这段代码很简单,在构建 RestClient 的过程中插入了一段代码,修改了请求头和响应头,用来兼容ES版本。只需要把这个类注入到Spring Bean中,就可以被 ElasticsearchRestClientConfigurations 自动加载。

3.2.5. WARN trationDelegate$BeanPostProcessorChecker: is not eligible for getting processed....

如图所示,我的项目在升级到 SpringBoot3.x 后出现了大量的 WARN:

虽然不影响项目运行,但是看得我很不爽,那只能想想办法看怎么解决掉这个warn了。

随机截取的一段异常信息,其他的也都差不多:

2024-01-28T11:58:45.587+08:00 WARN 1228 --- [user-server] [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration' of type [org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected into a currently created BeanPostProcessor [lbRestClientPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies.

注意看上面的异常信息,有任何跟我项目有关的东西吗?没有吧

那有任何跟依赖冲突有关的东西吗?看上去也没有

那这个异常什么时候才开始有的?Spring整体升级之后

好,那既然这样,我们可以大胆的认为这是一个SpringBoot的bug。

一顿搜索之后,我在github上找到了这个 issue:github.com/spring-clou...

还真是Spring的bug,不过不是SpringBoot,而是SpringCloud的bug。

这位官方大佬也说了,将会在下一个版本(2023.0.1)中修复它,预计2月20日(今天是1月28日),不过他们会先发布新的Commons,用以修复这个bug。

在我看到这个issue的时候,新版的 SpringCloudCommons已经发布了:spring.io/blog/2024/0...

于是我对项目中的依赖进行替换,由于这个依赖是从其他Spring-Cloud的组件中自动继承过来的,所以我们只需要在依赖管理里面指定下版本就可以了。

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-commons</artifactId>
      <version>4.1.1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

添加完之后,果然没有再报warn了,之后等 SpringCloud2023.0.1 发布了,再做一下替换就好了。

3.2.6. 更新自动注入文件

SpringBoot2.7时已经提出使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 代替 spring.factories

github.com/spring-proj...

我升级到 SpringBoot3.2 时,还是支持 spring.factories 的,但再过几个版本可能就不支持了,这边建议直接迁移下,这块几乎没什么成本的。

3.2.7. 源代码

这部分升级的改动有点多,以为已经搞好了,就提PR到main分支了,结果又蹦出来新的问题。

建议需要升级 SpringBoot3.x 的朋友,在看完这篇文章之后,还是再去把官方文档过一遍,看看有没有其他受影响的地方,这样稳妥一点。

代码已提交到GitHub:

源码建议单个 commit 结合 commit message 来查看,这样会更有条理,而不是整个 pr 一起看。

最后也附上一些我参考到的官方链接:

4. 后记

本来以为我这小项目简单升级下一两天就弄好了,结果前前后后搞了两周,尤其升级 SpringBoot 的时候,出了一顿问题,踩了不少坑。

看在作者这么认真的份上,建议关注趁早关注下,等我以后火了,在坐的各位就都是老粉了!

相关推荐
2202_754421542 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介5 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习5 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea
HoneyMoose7 分钟前
IDEA 2024.3 版本更新主要功能介绍
java·ide·intellij-idea
我只会发热9 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
是老余10 分钟前
本地可运行,jar包运行错误【解决实例】:通过IDEA的maven package打包多模块项目
java·maven·intellij-idea·jar
crazy_wsp11 分钟前
IDEA怎么定位java类所用maven依赖版本及引用位置
java·maven·intellij-idea
.Ayang13 分钟前
tomcat 后台部署 war 包 getshell
java·计算机网络·安全·web安全·网络安全·tomcat·网络攻击模型
bjzhang7518 分钟前
SpringBoot开发——Maven多模块工程最佳实践及详细示例
spring boot·maven·maven多模块工程
一直学习永不止步19 分钟前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表