1 前言

JDK8虽然非常好,但是JDK版本已经发布到JDK20了,且JDK8后的版本升级了很多新的特性,如模块化、ZGC以及虚拟线程、结构性并发等,也是非常有吸引力的,所以决定将基于JDK8的项目升级到最近的LTS版本JDK17。

2 升级过程记录

2.1 安装JDK17

下载JDK17的最新版本jdk-17_linux-x64_bin.tar.gz,解压缩后移动到/usr/lib/jvm/目录下

$ sudo su -# tar -xzf jdk-17_linux-x64_bin.tar.gz# mv jdk-17.0.2 /usr/lib/jvm/java-17复制代码

然后修改~/.bashrc,设置java相关环境变量为JDK17

# vim ~/.bashrcexport JAVA_HOME=/usr/lib/jvm/java-17export JRE_HOME=${JAVA_HOME}/jreexport CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/libexport PATH=${JAVA_HOME}/bin:$PATH复制代码

环境变量生效后,检查当前的jdk版本为JDK17

# source ~/.bashrc# java -versionopenjdk version "17.0.2" 2022-01-18OpenJDK Runtime Environment (build 17.0.2+8-86)OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)复制代码

2.2 升级spring版本并编译

修改项目的pom.xml文件,将spring boot和spring cloud版本由

org.springframework.bootspring-boot-starter-parent2.1.12.RELEASE Greenwich.SR3复制代码

修改为最新正式发布版本:

org.springframework.bootspring-boot-starter-parent3.0.6 2022.0.2复制代码

编译项目,报以下错误:

程序包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复制代码

原因是resilience4jCheckedSupplier接口新版本没有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异常类的构造函数发生了变化,根据需要将旧代码:

@Beanpublic ErrorDecoder feignError() {return (key, response) -> {if (response.status() >= 500) {FeignException exception = FeignException.errorStatus(key, response);return new RetryableException(response.status(),exception.getMessage(),response.request().httpMethod(),new Date());}// 其他异常交给Default去解码处理return defaultErrorDecoder.decode(key, response);};}复制代码

改为以下代码

@Beanpublic ErrorDecoder feignError() {return (key, response) -> {if (response.status() >= 500) {FeignException exception = FeignException.errorStatus(key, response);return new RetryableException(response.status(),exception.getMessage(),response.request().httpMethod(),new Date(),response.request());}// 其他异常交给Default去解码处理return defaultErrorDecoder.decode(key, response);};}复制代码

改为后继续编译,报以下错误:

程序包org.junit不存在程序包org.junit.runner不存在程序包junit.framework不存在复制代码

这是因为旧版本使用的是junit4,改为junit5相应的注解。即将:

import org.junit.Before;import org.junit.Ignore;import org.junit.Test;import org.junit.runner.RunWith;@Ignore@RunWith(MockitoJUnitRunner.class)public class FileSyncerTest {@Beforepublic void setUp() {}@Testpublic void testCase1() throws Exception {}}复制代码

改为

import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Disabled;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.mockito.junit.jupiter.MockitoExtension;@Disabled@ExtendWith(MockitoExtension.class)public class FileSyncerTest {@BeforeEachpublic void setUp() {}@Testpublic void testCase1() throws Exception {}}复制代码

改为后继续编译,编译通过。

[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 @7634b327at 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.(ReflectUtils.java:46)复制代码

这是因为从JDK9开始支持模块化了,项目中使用的部分组件可能还没有支持模块化,所以需要在jar包启动时添加add-opens jvm启动参数参数,我是通过在pom文件中添加build参数实现的:

org.springframework.bootspring-boot-maven-plugin--add-opens java.base/java.lang=ALL-UNNAMEDorg.projectlomboklombok复制代码

修改完后重新编译启动,启动仍然失败,报以下错误:

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerExceptionCaused by: java.lang.NullPointerException: null复制代码

这是因为项目中使用了knife4j,由于版本比较低,底层依赖的是spring-fox,支持的是openapi 2.x版本,而spring boot 3.0只支持openapi 3.x版本,所以knife4j版本依赖由:

com.github.xiaoyminknife4j-spring-boot-starter2.0.5复制代码

改为:

com.github.xiaoyminknife4j-openapi3-jakarta-spring-boot-starter4.1.0复制代码

同时将swagger的相关注解@Api@ApiOperation@ApiParam@ApiModel@ApiModelProperty替换为openapi3对应的注解:@Tag@Operation@Parameter@Schema@SchemaProperty

修改完后,重新编译启动,这次能正常启动了

但是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()即可,代码示例如下:

@Configurationpublic class WebCorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS").allowCredentials(true).maxAge(3600).allowedHeaders("*");}}复制代码

到此升级完成!

作者:movee
链接:https://juejin.cn/post/7229250736115138621