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 ~/.bashrc
export JAVA_HOME=/usr/lib/jvm/java-17
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
复制代码
環境変数が有効になったら、現在の jdk バージョンが JDK17 であることを確認します。
# source ~/.bashrc
# java -version
openjdk version "17.0.2" 2022-01-18
OpenJDK 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 のバージョンを次のように変更します。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
复制代码
最新の公式リリース バージョンに変更します。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring-cloud.version>2022.0.2</spring-cloud.version>
</properties>
复制代码
プロジェクトをコンパイルし、次のエラーを報告します。
程序包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
例外クラスのコンストラクターが変更され、必要に応じて古いコードが変更されたためです。
@Bean
public 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);
};
}
复制代码
次のコードに変更します
@Bean
public 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 {
@Before
public void setUp() {
}
@Test
public 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 {
@BeforeEach
public void setUp() {
}
@Test
public 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 @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参数实现的:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 添加 add-opens jvm参数 -->
<jvmArguments>--add-opens java.base/java.lang=ALL-UNNAMED</jvmArguments>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
复制代码
修改完后重新编译启动,启动仍然失败,报以下错误:
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版本依赖由:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.5</version>
</dependency>
复制代码
改为:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
复制代码
同时将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()
即可,代码示例如下:
@Configuration
public class WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
复制代码
到此升级完成!