JDK8升级JDK17过程中遇到的那些坑

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

1 前言
JDK8虽然非常好,但是JDK版本已经发布到JDK20了,且JDK8后的版本升级了很多新的特性,如模块化、ZGC以虚拟线程、结构性并发等,也是非常有吸引力的,所以决定将基于JDK8的项目升级到最近的LTS版本JDK17。
2 升级过程记录
2.1 安装JDK17
下载JDK17的最新版本jdk-17_linux-x64_bin.tar.gz,解压缩后移动到/usr/lib/jvm/目录下
shell复制代码$ sudo su -

tar -xzf jdk-17_linux-x64_bin.tar.gz

mv jdk-17.0.2 /usr/lib/jvm/java-17

修改~/.bashrc,设置java相关环境变量为JDK17
bash复制代码# 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
arduino复制代码# 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版本由
xml复制代码
org.springframework.boot
spring-boot-starter-parent
2.1.12.RELEASE

<properties>
    <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>

修改为最新正式发布版本:
xml复制代码
org.springframework.boot
spring-boot-starter-parent
3.0.6

<properties>
    <spring-cloud.version>2022.0.2</spring-cloud.version>
</properties>

编译项目,报以下错误:
复制代码程序包javax.servlet.http不存在
程序包javax.validation不存在

原因是原先javax包的名字改为jakarta了,将项目中所有依赖javax包的地方替换为jakarta
继续编译,报以下错误:
csharp复制代码[ERROR] 找不到符号
[ERROR] 符号: 类 EnableEurekaClient
[ERROR] 位置: 程序包 org.springframework.cloud.netflix.eureka

原因是新版本没有@EnableEurekaClient注解了,替换为@EnableDiscoveryClient
继续编译,报以下错误:
vbnet复制代码[ERROR] 找不到符号
[ERROR] 符号: 方法 apply()
[ERROR] 位置: 接口 io.github.resilience4j.core.functions.CheckedSupplier

原因是resilience4j的CheckedSupplier接口新版本没有apply()方法了,改为get()方法
继续编译,报以下错误:
scss复制代码[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异常类的构造函数发生了变化,根据需要将旧代码:
scss复制代码 @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);
    };
}

改为以下代码
scss复制代码 @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相应的注解。即将:
java复制代码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 {

}

}

改为
java复制代码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 {

}

}

改为后继续编译,编译通过。
less复制代码[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 启动项目
编译通过后启动项目,启动失败,报以下错误:
csharp复制代码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.(ReflectUtils.java:46)

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


org.springframework.boot
spring-boot-maven-plugin


--add-opens java.base/java.lang=ALL-UNNAMED


org.projectlombok
lombok





修改完后重新编译启动,启动仍然失败,报以下错误:
csharp复制代码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版本依赖由:
xml复制代码
com.github.xiaoymin
knife4j-spring-boot-starter
2.0.5

改为:
xml复制代码
com.github.xiaoymin
knife4j-openapi3-jakarta-spring-boot-starter
4.1.0

同时将swagger的相关注解@Api、@ApiOperation、@ApiParam、@ApiModel 、@ApiModelProperty替换为openapi3对应的注解:@Tag、@Operation、 @Parameter、 @Schema、 @SchemaProperty
最后将swagger的配置类内容由
less复制代码 @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;
}

改为:
less复制代码 @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访问项目接口时报以下错误:
less复制代码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()即可,代码示例如下:
typescript复制代码@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等)无法访问,要想启动时系统读取bootstrap.yml中的配置,使bootstrap.yml文件中的配置生效,需要在项目的pom.xml文件中添加下面的依赖:
xml复制代码

org.springframework.cloud
spring-cloud-starter-bootstrap

到此升级完成!
更进一步
之前项目中使用的GC收集器是CMS收集器,CMS收集器的调参非常繁琐,非常考验工程师的功底。ZGC声称能保证8MB~16TB的堆内存范围内都能保证GC的停顿时间在毫秒(官方声称小于10ms,也有些文章表示实际只有1、2ms)级别,尤其是一般情况下只要设置几个基本的GC参数就能让GC收集器很好的工作了,简直是工程师的福音。随着ZGC在JDK 15中正式GA,趁着升级JDK 17的机会立即把ZGC用起来。
设置几个基本的ZGC参数:
ini复制代码# 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"

相关文章
|
25天前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
19 1
|
1月前
|
Java 关系型数据库 开发工具
idea创建不了spring2.X版本,无法使用JDK8,最低支持JDK17 , 如何用idea创建spring2.X版本,使用JDK8解决方案
本文提供了解决方案,如何在IDEA中创建Spring 2.X版本的项目并使用JDK8,尽管Spring 2.X已停止维护且IDEA不再直接支持,通过修改pom.xml或使用阿里云的国内源来创建项目。
55 0
idea创建不了spring2.X版本,无法使用JDK8,最低支持JDK17 , 如何用idea创建spring2.X版本,使用JDK8解决方案
|
1月前
|
Oracle Java 关系型数据库
jdk17安装全方位手把手安装教程 / 已有jdk8了,安装JDK17后如何配置环境变量 / 多个不同版本的JDK,如何配置环境变量?
本文提供了详细的JDK 17安装教程,包括下载、安装、配置环境变量的步骤,并解释了在已有其他版本JDK的情况下如何管理多个JDK环境。
320 0
|
2月前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
|
3月前
|
安全 Java 测试技术
一文帮你搞定JDK8升级11
本文记录了作者从JDK8升级到11的实践过程和升级后的效果以及JDK11新玩法。
|
3月前
|
存储 Java
【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
|
3月前
|
Oracle Java 关系型数据库
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
|
2月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
266 3
|
3月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
56 1
|
3月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
663 4