JDK8升級(jí)JDK17過(guò)程中踩到的一些坑
1 前言
JDK8雖然非常好,但是JDK版本已經(jīng)發(fā)布到JDK20了,且JDK8后的版本升級(jí)了很多新的特性,如模塊化、ZGC以及虛擬線程、結(jié)構(gòu)性并發(fā)等,也是非常有吸引力的,所以決定將基于JDK8的項(xiàng)目升級(jí)到最近的LTS版本JDK17。
2 升級(jí)過(guò)程記錄
2.1 安裝JDK17
下載JDK17的最新版本jdk-17_linux-x64_bin.tar.gz
,解壓縮后移動(dòng)到/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
,設(shè)置java相關(guān)環(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)境變量生效后,檢查當(dāng)前的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 升級(jí)spring版本并編譯
修改項(xiàng)目的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>
編譯項(xiàng)目,報(bào)以下錯(cuò)誤:
程序包javax.servlet.http不存在
程序包javax.validation不存在
原因是原先javax
包的名字改為jakarta
了,將項(xiàng)目中所有依賴javax
包的地方替換為jakarta
繼續(xù)編譯,報(bào)以下錯(cuò)誤:
[ERROR] 找不到符號(hào)
[ERROR] 符號(hào): 類 EnableEurekaClient
[ERROR] 位置: 程序包 org.springframework.cloud.netflix.eureka
原因是新版本沒(méi)有@EnableEurekaClient
注解了,替換為@EnableDiscoveryClient
繼續(xù)編譯,報(bào)以下錯(cuò)誤:
[ERROR] 找不到符號(hào)
[ERROR] 符號(hào): 方法 apply()
[ERROR] 位置: 接口 io.github.resilience4j.core.functions.CheckedSupplier<java.lang.Object>
原因是resilience4j
的CheckedSupplier
接口新版本沒(méi)有apply()
方法了,改為get()
方法
繼續(xù)編譯,報(bào)以下錯(cuò)誤:
[ERROR] 對(duì)于RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.util.Date), 找不到合適的構(gòu)造器
[ERROR] 構(gòu)造器 feign.RetryableException.RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.lang.Throwable,java.util.Date,feign.Request)不適用
[ERROR] (實(shí)際參數(shù)列表和形式參數(shù)列表長(zhǎng)度不同)
[ERROR] 構(gòu)造器 feign.RetryableException.RetryableException(int,java.lang.String,feign.Request.HttpMethod,java.util.Date,feign.Request)不適用
[ERROR] (實(shí)際參數(shù)列表和形式參數(shù)列表長(zhǎng)度不同)
原因是openfeign
新版本的RetryableException
異常類的構(gòu)造函數(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ù)編譯,報(bào)以下錯(cuò)誤:
程序包org.junit不存在
程序包org.junit.runner不存在
程序包junit.framework不存在
這是因?yàn)榕f版本使用的是junit4
,改為junit5
相應(yīng)的注解。即將:
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ù)編譯,編譯通過(guò)。
[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 啟動(dòng)項(xiàng)目
編譯通過(guò)后啟動(dòng)項(xiàng)目,啟動(dòng)失敗,報(bào)以下錯(cuò)誤:
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)
這是因?yàn)閺腏DK9開(kāi)始支持模塊化了,項(xiàng)目中使用的部分組件可能還沒(méi)有支持模塊化,所以需要在jar包啟動(dòng)時(shí)添加add-opens
jvm啟動(dòng)參數(shù)參數(shù),我是通過(guò)在pom文件中添加build參數(shù)實(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>
修改完后重新編譯啟動(dòng),啟動(dòng)仍然失敗,報(bào)一下錯(cuò)誤:
Caused by: java.lang.IllegalArgumentException: @RequestMapping annotation not allowed on @FeignClient interfaces
根據(jù)錯(cuò)誤提示,@RequestMapping注解不能添加在@FeignClient接口上了,改為通過(guò)在@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 { }
修改完后重新編譯啟動(dòng),啟動(dòng)仍然失敗,報(bào)以下錯(cuò)誤:
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
Caused by: java.lang.NullPointerException: null
這是因?yàn)轫?xiàng)目中使用了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>
同時(shí)將swagger的相關(guān)注解@Api
、@ApiOperation
、@ApiParam
、@ApiModel
、@ApiModelProperty
替換為openapi3
對(duì)應(yīng)的注解:@Tag
、@Operation
、 @Parameter
、 @Schema
、 @SchemaProperty
最后將swagger的配置類內(nèi)容由
@Bean(value = "oasConfig") public Docket oasConfig() { Docket docket=new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .title("spring-project-framework") .description("spring項(xiàng)目骨架") .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項(xiàng)目骨架") .version("v1"))) .packagesToScan("movee.api.v1") .build(); }
修改完后,重新編譯啟動(dòng),這次能正常啟動(dòng)了
但是web訪問(wèn)項(xiàng)目接口時(shí)報(bào)以下錯(cuò)誤:
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
這個(gè)是跨域的問(wèn)題,新版本spring MVC的CorsRegistry
已經(jīng)沒(méi)有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("*"); } }
這時(shí)因?yàn)橹绊?xiàng)目的spring cloud
、actuator
的相關(guān)配置寫(xiě)在bootstrap.yml
文件中,升級(jí)到spring boot 3
之后actuator的端點(diǎn)(如/actuator/info
等)無(wú)法訪問(wèn),要想啟動(dòng)時(shí)系統(tǒng)讀取bootstrap.yml中的配置,使bootstrap.yml文件中的配置生效,需要在項(xiàng)目的pom.xml文件中添加下面的依賴:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies>
到此升級(jí)完成!
3 更進(jìn)一步
之前項(xiàng)目中使用的GC收集器是CMS收集器,CMS收集器的調(diào)參非常繁瑣,非??简?yàn)工程師的功底。ZGC
聲稱能保證8MB~16TB的堆內(nèi)存范圍內(nèi)都能保證GC的停頓時(shí)間在毫秒(官方聲稱小于10ms,也有些文章表示實(shí)際只有1、2ms)級(jí)別,尤其是一般情況下只要設(shè)置幾個(gè)基本的GC參數(shù)就能讓GC收集器很好的工作了,簡(jiǎn)直是工程師的福音。隨著ZGC在JDK 15中正式GA,趁著升級(jí)JDK 17的機(jī)會(huì)立即把ZGC用起來(lái)。 設(shè)置幾個(gè)基本的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"
總結(jié)
到此這篇關(guān)于JDK8升級(jí)JDK17過(guò)程中踩到的一些坑的文章就介紹到這了,更多相關(guān)JDK8升級(jí)JDK17踩坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA配置Tomcat創(chuàng)建web項(xiàng)目的詳細(xì)步驟
Tomcat是一個(gè)Java?Web應(yīng)用服務(wù)器,實(shí)現(xiàn)了多個(gè)Java?EE規(guī)范(JSP、Java?Servlet等),這篇文章主要給大家介紹了關(guān)于IDEA配置Tomcat創(chuàng)建web項(xiàng)目的詳細(xì)步驟,需要的朋友可以參考下2023-12-12SpringCloud如何利用Feign訪問(wèn)外部http請(qǐng)求
這篇文章主要介紹了SpringCloud如何利用Feign訪問(wèn)外部http請(qǐng)求,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03springmvc+Hibernate+JPA(混合事務(wù))解讀
在Spring項(xiàng)目中,Spring Data JPA作為一種持久層框架,因其簡(jiǎn)化數(shù)據(jù)庫(kù)操作而受到青睞,但在將其引入使用Hibernate的舊項(xiàng)目時(shí),可能會(huì)遇到事務(wù)處理問(wèn)題,解決方案包括配置兩種事務(wù)管理器:Hibernate事務(wù)管理器和JPA事務(wù)管理器2024-09-09深度deepin安裝以及jdk、tomcat、Nginx安裝教程
這篇文章主要給大家介紹了關(guān)于深度deepin安裝以及jdk、tomcat、Nginx安裝的相關(guān)資料,按照文中介紹的方法可以輕松的實(shí)現(xiàn)安裝,對(duì)大家的工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01Java如何實(shí)現(xiàn)http接口參數(shù)和返回值加密
這篇文章主要介紹了Java如何實(shí)現(xiàn)http接口參數(shù)和返回值加密問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目
這篇文章主要為大家介紹了Quarkus的Spring項(xiàng)目擴(kuò)展,帶大家快速改造Spring項(xiàng)目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02使用SpringAOP實(shí)現(xiàn)公共字段填充功能
在新增員工或者新增菜品分類時(shí)需要設(shè)置創(chuàng)建時(shí)間、創(chuàng)建人、修改時(shí)間、修改人等字段,在編輯員工或者編輯菜品分類時(shí)需要設(shè)置修改時(shí)間、修改人等字段,這些字段屬于公共字段,本文將給大家介紹使用SpringAOP實(shí)現(xiàn)公共字段填充功能,需要的朋友可以參考下2024-08-08