踩了一堆坑,终于把微服务系统全面升级 JDK17 和 SpringBoot3 了
最近正在给自己的开源项目校园博客升级到 JDK17 以及 SpringBoot3正好记录下升级和踩坑的过程给大家提供一些解决方案的参考。先说结论非常推荐升级JDK17成本低收益高。至于SpringBoot3.0迁移成本比较高坑也会比较多但如果是新项目的话还是可以试试的。PS项目原来的版本是 JDK8 SpringBoot2.6。为什么要升级JDK17和SpringBoot3也发布了一段时间了自己对一些新特性也比较感兴趣尤其是 Native Image 这个玩意。自己手上刚好有校园博客这个项目可以用来给进行升级项目不复杂但也算五脏俱全全量升级既可以感受一下变化也不会太费事。JDK17 是一个长期支持的版本LTS现在很多开源应用或者一些组件都在往这上面靠并且大有一种最低支持 JDK17 的趋势。自己在公司所接触到的项目也有一部分是使用的JDK17并且整体有往这方面靠的趋势新项目都会直接用JDK17。总的来说就是 兴趣 资源 趋势。升级有什么好处先来看看 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高多了。升级过程分享以下的一切内容均基于我已有的项目【校园博客】进行升级和讲解源码地址https://github.com/stick-i/scblogs既然一切都是基于JDK17的那我们就先升级JDK吧升级JDK17下载安装安装JDK17这里我直接在IDEA里面下载安装了很方便为了便于自己以后使用 Native Image这里我直接下载了 GraalVM。在IDEA中更新项目SDK和模块SDKMaven构建更新Maven编译配置properties java.version17/java.version maven.compiler.source${java.version}/maven.compiler.source maven.compiler.target${java.version}/maven.compiler.target /propertiesMaven重新打包下这一步主要是为了更新下内部组件的 JDK 版本。启动服务测试下有没有其他问题启动所有微服务项目竟然一切正常也可能与我的项目比较简单有一定的关系所有服务都成功跑起来了。更新Dockerfile原来使用的基础镜像是java:8-alpine更新到了亚马逊的openjdk17版本amazoncorretto:17-alpine。# 设置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:MetaspaceSize256m \ -XX:MaxMetaspaceSize512m \ -Duser.timezoneGMT08 #空参数方便创建容器时传参 ENV PARAMS # 入口点 执行JAVA运行命令 ENTRYPOINT [sh,-c,java -jar $JAVA_OPTS /app.jar $PARAMS]源代码看起来没什么问题了先提交上JDK升级的代码有需要的同学可以查看提交记录https://github.com/stick-i/scblogs/pull/198/commits升级SpringBoot3.2为什么选择直接升级到 SpringBoot3.2 而不是 3.0呢主要是我开始升级的时候SpringBoot已经更新到3.2了而此时的3.0的生命周期已经过半了目前也还没有推出3.0以上的LTS版本这么看来我以后总还是要升级的倒不如现在一起弄了。升级pom依赖跟SpringBoot相关的依赖还是比较多的尤其是依赖了SpringCore的三方依赖肯定也是要统一进行升级的。截至到我写这篇文章的时间SpringBoot的最新GA版本为 3.2.1我选择相信Spring直接升级最新版对应的SpringCloud版本为2023.0.0其他主要依赖对应升级的情况依赖项升级前版本升级后版本备注SpringBoot2.6.113.2.1目前的最新版要踩坑就踩最新的坑SpringCloud2021.0.42023.0.0对应SpringBoot3.2.xSpringCloudAlibaba2021.0.4.02022.0.0这个库还没出2023的版本但是2022版也是基于SpringBoot3.0的应该不会差太多Mybatis-Plus3.5.3.13.5.5注意artifactId 从mybatis-plus-boot-starter改成了mybatis-plus-spring-boot3-starterdruid1.2.111.2.20注意artifactId 从druid-spring-boot-starter改成了druid-spring-boot-3-starter对了建议顺便升级下Maven。解决依赖异常修改完pom文件之后刷新一下本地依赖包噢呦一堆报错我看了一下就两个问题分别是 mysql-connector-java 和 javax.servlet-api 这两个包的版本没有被指定所以Maven找不到对应的包。为什么没有指定呢之前也没有指定但是之前没有报错说明这两个包之前是有被 spring-boot-starter-parent 所管理的但是现在它不管了。这得去看看SpringBoot3.0的迁移文档https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-GuideMySQL在网页里搜索关键字 MySQL这不就来了就是说mysql:mysql-connector-java这个包的坐标改成了com.mysql:mysql-connector-j让我们更新的时候也顺带改一下。这个简单全局搜索然后改一下就好了。一改完版本继承的小图标就出来了不错不错。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()) 代替了原来的方法。做完上面这些后我的项目已经可以成功编译了但还不能正常的跑起来。配置文件属性迁移SpringBoot3 更改了一些配置属性例如spring.redis.host改为了spring.data.redis.host。这一变更几乎对所有项目都会有影响要查看所有的变更项可以在官方文档中进行查找https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Configuration-Changelog但这太silly了很显然官方也这么认为所以给开发者提供了一个简单的迁移方法**引入**spring-boot-properties-migrator****它会帮你自动检测配置文件中需要修改的地方dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-properties-migrator/artifactId scoperuntime/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都是没有给弃用原因的。既然这样那我只能自己去官方文档里找了https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Configuration-Changelog全局搜索下lettuce.pool你别说还真让我找到了明明也没有弃用就是把redis前面加个data罢了看来spring-boot-properties-migrator也偶有瞎说的情况啊。再次提醒配置文件属性迁移完毕后记得删除之前添加的 spring-boot-properties-migrator 依赖。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 上找到一篇帖子里面有对这个问题的描述可以参考下https://stackoverflow.com/questions/71142680/co-elastic-clients-transport-transportexception-es-search-missing-x-elastic它讲到了两个问题客户端向服务端发送了未知的 Content-Type 因此其请求被拒绝并返回 406其实是请求头 compatible-with 不受支持客户端需要验证 response 中是否具有 X-Elastic-ProductElasticsearch 标头但服务端并没有返回这个。问题其实蛮清晰的但是给出的解决方案让我不太满意还需要自己重新去构建一个 RestClient自己读取配置文件然后set进去又得设置账号密码、又得解析Host的这我可受不了。于是经过我的一顿研究之后我发现了RestClientBuilderCustomizer这个类翻译回调接口可以由希望通过RestClientBuilder进一步定制RestClient的bean实现同时保留默认的自动配置。只要用这个玩意就可以在原有的 RestClient 基础上进行一些定制化的操作比如说解决上面那两个问题。于是乎我就写了下面这一段代码/** * 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 自动加载。WARN trationDelegate$BeanPostProcessorChecker: is not eligible for getting processed....如图所示我的项目在升级到 SpringBoot3.x 后出现了大量的 WARN虽然不影响项目运行但是看得我很不爽那只能想想办法看怎么解决掉这个warn了。随机截取的一段异常信息其他的也都差不多2024-01-28T11:58:45.58708: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上找到了这个 issuehttps://github.com/spring-cloud/spring-cloud-commons/issues/1315还真是Spring的bug不过不是SpringBoot而是SpringCloud的bug。这位大佬也说了将会在下一个版本2023.0.1中修复它预计2月20日今天是1月28日不过他们会先发布新的Commons用以修复这个bug。在我看到这个issue的时候新版的 SpringCloudCommons已经发布了https://spring.io/blog/2024/01/23/spring-cloud-commons-4-1-1-has-been-released于是我对项目中的依赖进行替换由于这个依赖是从其他Spring-Cloud的组件中自动继承过来的所以我们只需要在依赖管理里面指定下版本就可以了。dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-commons/artifactId version4.1.1/version scopecompile/scope /dependency /dependencies /dependencyManagement添加完之后果然没有再报warn了之后等 SpringCloud2023.0.1 发布了再做一下替换就好了。更新自动注入文件SpringBoot2.7时已经提出使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports代替spring.factorieshttps://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#changes-to-auto-configuration我升级到 SpringBoot3.2 时还是支持 spring.factories 的但再过几个版本可能就不支持了这边建议直接迁移下这块几乎没什么成本的。源代码这部分升级的改动有点多以为已经搞好了就提PR到main分支了结果又蹦出来新的问题。建议需要升级 SpringBoot3.x 的朋友在看完这篇文章之后还是再去把官方文档过一遍看看有没有其他受影响的地方这样稳妥一点。