踩了一堆坑,终于把微服务系统全面升级 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文本块

实用性很强,非常舒服。

image.png


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

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

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

image.png

升级过程分享

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

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

升级JDK17

下载安装

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

image.png

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

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

image.png

Maven构建

更新Maven编译配置:

<properties>
  <java.version>17</java.version>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

Maven重新打包下:

image.png

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

启动服务

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

image.png

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

更新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:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-Duser.timezone=GMT+08 "
#空参数,方便创建容器时传参
ENV PARAMS=""
# 入口点, 执行JAVA运行命令
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]

源代码

看起来没什么问题了,先提交上JDK升级的代码,有需要的同学可以查看提交记录:
build(all): 全量升级到jdk17,更新dockerfile和pom文件。 by stick-i · Pull Request #198 · stick-i/scblogs

升级SpringBoot3.2

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

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

image.png

升级pom依赖

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

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

image.png

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

image.png

对应的SpringCloud版本为2023.0.0

image.png

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

依赖项升级前版本升级后版本备注
SpringBoot2.6.113.2.1目前的最新版,要踩坑就踩最新的坑🤡
SpringCloud2021.0.42023.0.0对应SpringBoot3.2.x
SpringCloudAlibaba2021.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-starter
druid1.2.111.2.20注意:artifactId 从druid-spring-boot-starter改成了druid-spring-boot-3-starter

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

解决依赖异常

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

image.png

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

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

image.png

这得去看看SpringBoot3.0的迁移文档:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide

MySQL

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

image.png

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

image.png

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

image.png

javax -> jakarta

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

image.png

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

先更新下pom文件:

image.png

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

image.png

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

image.png

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>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
  <scope>runtime</scope>
</dependency>

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

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

image.png

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

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,你别说,还真让我找到了:

image.png

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

image.png

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

ES版本兼容

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

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

image.png

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

image.png

它讲到了两个问题:

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

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

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

image.png

翻译:回调接口,可以由希望通过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:

image.png

虽然不影响项目运行,但是看得我很不爽,那只能想想办法看怎么解决掉这个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。

image.png

一顿搜索之后,我在github上找到了这个 issue:https://github.com/spring-cloud/spring-cloud-commons/issues/1315

image.png

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

image.png

这位大佬也说了,将会在下一个版本(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

image.png

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

<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 发布了,再做一下替换就好了。

image.png

更新自动注入文件

SpringBoot2.7时已经提出使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 代替 spring.factories
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#changes-to-auto-configuration

image.png

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

image.png

源代码

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

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

代码已提交到GitHub:

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

image.png

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

后记

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

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

454x370.png

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,让我来为您解答这个问题。 首先,需要明确的是,JDK17和Netty是Java开发中非常常用的工具和框架,而Spring Boot是一个基于Spring框架的快速开发工具,可以帮助我们快速搭建Java Web应用程序。 接下来,我将为您提供一个使用JDK17、Netty和Spring Boot搭建IoT项目的简单实例: 1. 确保您已经安装好了JDK17,并且已经配置好了环境变量。 2. 创建一个Spring Boot项目,可以使用工具如IntelliJ IDEA或Eclipse。在创建项目时,选择Maven或Gradle构建工具。 3. 在项目中添加Netty依赖,可以在pom.xml或build.gradle文件中添加以下依赖: ``` <!-- Maven --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.43.Final</version> </dependency> // Gradle compile group: 'io.netty', name: 'netty-all', version: '4.1.43.Final' ``` 4. 创建IoT服务类,例如MyIoTServer: ``` @Component public class MyIoTServer { @Value("${netty.port}") private int port; public void start() throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new MyIoTServerHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(port).sync(); channelFuture.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ``` 5. 创建IoT服务处理器类,例如MyIoTServerHandler: ``` @Component public class MyIoTServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理接收到的消息 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 处理异常 } } ``` 6. 在application.properties或application.yml文件中配置Netty端口号: ``` # application.properties netty.port=8080 # application.yml netty: port: 8080 ``` 7. 在启动类中启动IoT服务: ``` @SpringBootApplication public class MyApplication { public static void main(String[] args) throws InterruptedException { ApplicationContext context = SpringApplication.run(MyApplication.class, args); MyIoTServer myIoTServer = context.getBean(MyIoTServer.class); myIoTServer.start(); } } ``` 以上就是一个使用JDK17、Netty和Spring Boot搭建IoT项目的简单实例。当然,这只是一个初步的示例,实际开发中还需要根据具体需求进行相关的配置和开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿杆.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值