JDK8升級JDK17過程中踩到的一些坑
1 前言
JDK8雖然非常好,但是JDK版本已經發(fā)布到JDK20了,且JDK8后的版本升級了很多新的特性,如模塊化、ZGC以及虛擬線程、結構性并發(fā)等,也是非常有吸引力的,所以決定將基于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相關環(huán)境變量為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
環(huán)境變量生效后,檢查當前的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>修改為最新正式發(fā)布版本:
<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
繼續(xù)編譯,報以下錯誤:
[ERROR] 找不到符號
[ERROR] 符號: 類 EnableEurekaClient
[ERROR] 位置: 程序包 org.springframework.cloud.netflix.eureka
原因是新版本沒有@EnableEurekaClient注解了,替換為@EnableDiscoveryClient
繼續(xù)編譯,報以下錯誤:
[ERROR] 找不到符號
[ERROR] 符號: 方法 apply()
[ERROR] 位置: 接口 io.github.resilience4j.core.functions.CheckedSupplier<java.lang.Object>
原因是resilience4j的CheckedSupplier接口新版本沒有apply()方法了,改為get()方法
繼續(xù)編譯,報以下錯誤:
[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] (實際參數(shù)列表和形式參數(shù)列表長度不同)
[ERROR] 構造器 feign.RetryableException.RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.util.Date,feign.Request)不適用
[ERROR] (實際參數(shù)列表和形式參數(shù)列表長度不同)
原因是openfeign新版本的RetryableException異常類的構造函數(shù)發(fā)生了變化,根據(jù)需要將舊代碼:
@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);
};
}改為后繼續(xù)編譯,報以下錯誤:
程序包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 {
}
}改為后繼續(xù)編譯,編譯通過。
[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啟動參數(shù)參數(shù),我是通過在pom文件中添加build參數(shù)實現(xiàn)的:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 添加 add-opens jvm參數(shù) -->
<jvmArguments>
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-exports=java.base/sun.security.ssl=ALL-UNNAMED
--add-opens=java.base/sun.security.ssl.internal.ssl=ALL-UNNAMED
</jvmArguments>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>修改完后重新編譯啟動,啟動仍然失敗,報一下錯誤:
Caused by: java.lang.IllegalArgumentException: @RequestMapping annotation not allowed on @FeignClient interfaces
根據(jù)錯誤提示,@RequestMapping注解不能添加在@FeignClient接口上了,改為通過在@FeignClient注解的path屬性攜帶,即由:
@FeignClient(url = "127.0.0.1:8080", name = "service-feign")
@RequestMapping("/service")
public interface ServiceFeign {
}改為:
@FeignClient(url = "127.0.0.1:8080", name = "service-feign", path = "/service")
public interface ServiceFeign {
}修改完后重新編譯啟動,啟動仍然失敗,報以下錯誤:
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
最后將swagger的配置類內容由
@Bean(value = "oasConfig")
public Docket oasConfig() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("spring-project-framework")
.description("spring項目骨架")
.version("v1")
.build())
.groupName("backup-v1")
.select()
.apis(RequestHandlerSelectors.basePackage("movee.api.v1"))
.paths(PathSelectors.any())
.build();
return docket;
}改為:
@Bean(value = "oasConfig")
public GroupedOpenApi oasConfig(){
return GroupedOpenApi.builder()
.group("spring-project-framework-v1")
.addOpenApiCustomizer(api -> api.info(new Info()
.title("spring-project-framework")
.description("spring項目骨架")
.version("v1")))
.packagesToScan("movee.api.v1")
.build();
}修改完后,重新編譯啟動,這次能正常啟動了
但是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("*");
}
}這時因為之前項目的spring cloud、actuator的相關配置寫在bootstrap.yml文件中,升級到spring boot 3之后actuator的端點(如/actuator/info等)無法訪問,要想啟動時系統(tǒng)讀取bootstrap.yml中的配置,使bootstrap.yml文件中的配置生效,需要在項目的pom.xml文件中添加下面的依賴:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>到此升級完成!
3 更進一步
之前項目中使用的GC收集器是CMS收集器,CMS收集器的調參非常繁瑣,非??简灩こ處煹墓Φ住?code>ZGC聲稱能保證8MB~16TB的堆內存范圍內都能保證GC的停頓時間在毫秒(官方聲稱小于10ms,也有些文章表示實際只有1、2ms)級別,尤其是一般情況下只要設置幾個基本的GC參數(shù)就能讓GC收集器很好的工作了,簡直是工程師的福音。隨著ZGC在JDK 15中正式GA,趁著升級JDK 17的機會立即把ZGC用起來。 設置幾個基本的ZGC參數(shù):
# log
JVM_OPTS="$JVM_OPTS -XX:+PrintCommandLineFlags"
JVM_OPTS="$JVM_OPTS -Xlog:gc*:file=${LOG_DIR}/${PROJ_NAME}-gc-%p.log:time,uptime:filecount=10,filesize=50M"
JVM_OPTS="$JVM_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JVM_OPTS="$JVM_OPTS -XX:HeapDumpPath=${LOG_DIR}/${PROJ_NAME}-`date +%s`-pid$$.hprof"
JVM_OPTS="$JVM_OPTS -XX:ErrorFile=${LOG_DIR}/${PROJ_NAME}-`date +%s`-pid%p.log"
# memory
JVM_OPTS="$JVM_OPTS -Xms4g"
JVM_OPTS="$JVM_OPTS -Xmx4g"
JVM_OPTS="$JVM_OPTS --add-opens=java.base/java.lang=ALL-UNNAMED"
JVM_OPTS="$JVM_OPTS --add-opens=java.base/java.util=ALL-UNNAMED"
JVM_OPTS="$JVM_OPTS --add-exports=java.base/sun.security.ssl=ALL-UNNAMED"
JVM_OPTS="$JVM_OPTS --add-opens=java.base/sun.security.ssl.internal.ssl=ALL-UNNAMED"
# gc collector
JVM_OPTS="$JVM_OPTS -XX:+UseZGC"
# JVM_OPTS="$JVM_OPTS -XX:ConcGCThreads=4"
JVM_OPTS="$JVM_OPTS -XX:+UnlockDiagnosticVMOptions"
JVM_OPTS="$JVM_OPTS -XX:ZStatisticsInterval=10"總結
到此這篇關于JDK8升級JDK17過程中踩到的一些坑的文章就介紹到這了,更多相關JDK8升級JDK17踩坑內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解java中的深拷貝和淺拷貝(clone()方法的重寫、使用序列化實現(xiàn)真正的深拷貝)
這篇文章主要介紹了java中的深拷貝和淺拷貝(clone()方法的重寫、使用序列化實現(xiàn)真正的深拷貝),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-03-03
Java char[]數(shù)組轉成String類型詳細介紹
這篇文章詳細介紹了Java char[]數(shù)組轉成String類型(char to String)的方法,文章中有詳細的代碼示例,需要的朋友可以參考閱讀2023-04-04
cxf整合Spring實現(xiàn)webservice調用實踐
文章介紹了使用CXF整合Spring實現(xiàn)WebService的過程,包括pom文件配置、web.xml和applicationContext.xml文件配置、接口定義與實現(xiàn)、項目運行及測試,通過訪問wsdl地址驗證服務可用性,最后通過客戶端測試類驗證調用成功2026-04-04
在mybatis執(zhí)行SQL語句之前進行攔擊處理實例
本篇文章主要介紹了在mybatis執(zhí)行SQL語句之前進行攔擊處理實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

