1.概叙
科普文:软件架构JDK系列之【JDK8、11、17选型】-CSDN博客
科普文:软件架构JDK系列之【JDK8升级到JDK11】-CSDN博客
JDK8虽然非常好,但是JDK版本已经发布到JDK21了,且JDK8后的版本升级了很多新的特性,如模块化、ZGC以及虚拟线程、结构性并发等,也是非常有吸引力的,所以决定将基于JDK8的项目升级到最近的LTS版本JDK17。
2022年Spring6和 SpringBoot3相继推出,在此之前,Java社区一直是"新版任你发,我用Java 8",不管新版本怎么出,很少有人愿意升级。
这一次,Spring 直接来了个大招,SpringBoot3和Spring6的最低依赖就是JDK17!
跨过 JDK 8-16,直接升级到 JDK 17。
2 升级过程记录
下面是一些在升级过程中可能遇到的问题和解决方案:
- 依赖冲突:在升级过程中,可能会遇到依赖冲突问题。例如,某些库可能不再与新版本的JDK兼容。解决这个问题的方法是检查项目的依赖项,并确保它们与新版本的JDK兼容。如果遇到不兼容的库,可能需要寻找替代方案或等待库的更新。
- 编译错误:在某些情况下,代码可能在新版本的JDK中无法编译。这可能是由于语法或API更改导致的。解决这个问题的方法是检查编译错误并更新代码以适应新版本的JDK。务必阅读相关的Java官方文档,以了解新版本中的更改和注意事项。
- 运行时错误:即使代码能够成功编译,也可能会在运行时遇到问题。这可能是由于新版本的JDK中的运行时行为更改导致的。解决这个问题的方法是测试应用程序的所有功能,并确保一切正常工作。此外,可以使用Java的调试工具来诊断和解决运行时错误。
- 性能问题:新版本的JDK可能引入了性能改进,但也可能会对应用程序的性能产生负面影响。在升级之前,建议进行性能测试,并监控应用程序的性能指标。如果发现性能问题,可以尝试调整代码或配置选项来优化性能。
在实际升级过程中,以下是一些实用的建议:
- 备份数据:在开始升级之前,请确保备份所有重要的数据和配置文件。这有助于防止升级过程中发生意外情况导致数据丢失。
- 分阶段升级:如果可能的话,建议分阶段升级。首先在开发环境中进行测试,确保一切正常后,再在生产环境中进行升级。这样可以减少风险并更好地控制升级过程。
- 阅读官方文档:务必阅读新版本JDK的官方文档,了解新特性和变化。这有助于避免潜在的问题并更好地利用新版本的功能。
- 社区支持:如果遇到困难,不要犹豫寻求社区的支持。有许多在线论坛和社区专门讨论Java开发问题。与其他开发人员交流经验和解决方案可以帮助解决问题。
总之,从JDK 8升级到JDK 17是一个值得考虑的步骤,但也需要谨慎处理。通过仔细规划和充分准备,可以顺利完成升级并获得新版本JDK带来的好处。
2.1 安装JDK17
下载JDK17的最新版本jdk-17_linux-x64_bin.tar.gz
,解压缩后移动到/usr/lib/jvm/
目录下
1 2 3 |
|
然后修改~/.bashrc
,设置java相关环境变量为JDK17
1 2 3 4 5 6 |
|
环境变量生效后,检查当前的jdk版本为JDK17
1 2 3 4 5 6 |
|
2.2 升级spring版本并编译
修改项目的pom.xml
文件,将spring boot和spring cloud版本由
1 2 3 4 5 6 7 8 9 |
|
修改为最新正式发布版本:
1 2 3 4 5 6 7 8 9 |
|
编译项目,报以下错误:
程序包javax.servlet.http不存在
程序包javax.validation不存在
原因是原先javax
包的名字改为jakarta
了,将项目中所有依赖javax
包的地方替换为jakarta
继续编译,报以下错误:
[ERROR] 找不到符号
[ERROR] 符号: 类 EnableEurekaClient
[ERROR] 位置: 程序包 org.springframework.cloud.netflix.eureka
原因是新版本没有@EnableEurekaClient
注解了,替换为@EnableDiscoveryClient
继续编译,报以下错误:
[ERROR] 找不到符号
[ERROR] 符号: 方法 apply()
[ERROR] 位置: 接口 io.github.resilience4j.core.functions.CheckedSupplier<java.lang.Object>
原因是resilience4j
的CheckedSupplier
接口新版本没有apply()
方法了,改为get()
方法
继续编译,报以下错误:
[ERROR] 对于RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.util.Date), 找不到合适的构造器
[ERROR] 构造器 feign.RetryableException.RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.lang.Throwable,java.util.Date,feign.Request)不适用
[ERROR] (实际参数列表和形式参数列表长度不同)
[ERROR] 构造器 feign.RetryableException.RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.util.Date,feign.Request)不适用
[ERROR] (实际参数列表和形式参数列表长度不同)
原因是openfeign
新版本的RetryableException
异常类的构造函数发生了变化,根据需要将旧代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
改为以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
改为后继续编译,报以下错误:
程序包org.junit不存在
程序包org.junit.runner不存在
程序包junit.framework不存在
这是因为旧版本使用的是junit4
,改为junit5
相应的注解。即将:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
改为后继续编译,编译通过。
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.582 s (Wall Clock)
[INFO] Finished at: 2023-05-04T16:39:42+08:00
[INFO] Final Memory: 59M/214M
[INFO] ------------------------------------------------------------------------
2.3 启动项目
编译通过后启动项目,启动失败,报以下错误:
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @7634b327
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at net.sf.cglib.core.ReflectUtils$2.run(ReflectUtils.java:56)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
at net.sf.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:46)
这是因为从JDK9开始支持模块化了,项目中使用的部分组件可能还没有支持模块化,所以需要在jar包启动时添加add-opens
jvm启动参数参数,我是通过在pom文件中添加build参数实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
修改完后重新编译启动,启动仍然失败,报一下错误:
Caused by: java.lang.IllegalArgumentException: @RequestMapping annotation not allowed on @FeignClient interfaces
根据错误提示,@RequestMapping注解不能添加在@FeignClient接口上了,改为通过在@FeignClient注解的path属性携带,即由:
1 2 3 4 |
|
改为:
1 2 3 |
|
修改完后重新编译启动,启动仍然失败,报以下错误:
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
Caused by: java.lang.NullPointerException: null
这是因为项目中使用了knife4j,由于版本比较低,底层依赖的是spring-fox,支持的是openapi 2.x版本,而spring boot 3.0只支持openapi 3.x版本,所以knife4j版本依赖由:
1 2 3 4 5 |
|
改为:
1 2 3 4 5 |
|
同时将swagger的相关注解@Api
、@ApiOperation
、@ApiParam
、@ApiModel
、@ApiModelProperty
替换为openapi3
对应的注解:@Tag
、@Operation
、 @Parameter
、 @Schema
、 @SchemaProperty
最后将swagger的配置类内容由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
改为:
1 2 3 4 5 6 7 8 9 10 11 |
|
修改完后,重新编译启动,这次能正常启动了
但是web访问项目接口时报以下错误:
Caused by: java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
at org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:516)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:538)
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1275)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
... 36 common frames omitted
这个是跨域的问题,新版本spring MVC的CorsRegistry
已经没有allowedOrigin()
方法了,替换为新接口allowedOriginPatterns()
即可,代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这时因为之前项目的spring cloud
、actuator
的相关配置写在bootstrap.yml
文件中,升级到spring boot 3
之后actuator的端点(如/actuator/info
等)无法访问,要想启动时系统读取bootstrap.yml中的配置,使bootstrap.yml文件中的配置生效,需要在项目的pom.xml文件中添加下面的依赖:
1 2 3 4 5 6 |
|
到此升级完成!
3 更进一步
之前项目中使用的GC收集器是CMS收集器,CMS收集器的调参非常繁琐,非常考验工程师的功底。
ZGC
声称能保证8MB~16TB的堆内存范围内都能保证GC的停顿时间在毫秒(官方声称小于10ms,也有些文章表示实际只有1、2ms)级别,尤其是一般情况下只要设置几个基本的GC参数就能让GC收集器很好的工作了,简直是工程师的福音。
随着ZGC在JDK 15中正式GA,趁着升级JDK 17的机会立即把ZGC用起来。
设置几个基本的ZGC参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|