科普文:软件架构JDK系列之【JDK8升级到JDK17】

1.概叙 

科普文:软件架构JDK系列之【JDK8、11、17选型】-CSDN博客

科普文:软件架构JDK系列之【JDK8升级到JDK11】-CSDN博客

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

2022年Spring6和 SpringBoot3相继推出,在此之前,Java社区一直是"新版任你发,我用Java 8",不管新版本怎么出,很少有人愿意升级。

这一次,Spring 直接来了个大招,SpringBoot3和Spring6的最低依赖就是JDK17!

跨过 JDK 8-16,直接升级到 JDK 17。

2 升级过程记录

下面是一些在升级过程中可能遇到的问题和解决方案:

  1. 依赖冲突:在升级过程中,可能会遇到依赖冲突问题。例如,某些库可能不再与新版本的JDK兼容。解决这个问题的方法是检查项目的依赖项,并确保它们与新版本的JDK兼容。如果遇到不兼容的库,可能需要寻找替代方案或等待库的更新。
  2. 编译错误:在某些情况下,代码可能在新版本的JDK中无法编译。这可能是由于语法或API更改导致的。解决这个问题的方法是检查编译错误并更新代码以适应新版本的JDK。务必阅读相关的Java官方文档,以了解新版本中的更改和注意事项。
  3. 运行时错误:即使代码能够成功编译,也可能会在运行时遇到问题。这可能是由于新版本的JDK中的运行时行为更改导致的。解决这个问题的方法是测试应用程序的所有功能,并确保一切正常工作。此外,可以使用Java的调试工具来诊断和解决运行时错误。
  4. 性能问题:新版本的JDK可能引入了性能改进,但也可能会对应用程序的性能产生负面影响。在升级之前,建议进行性能测试,并监控应用程序的性能指标。如果发现性能问题,可以尝试调整代码或配置选项来优化性能。

在实际升级过程中,以下是一些实用的建议:

  • 备份数据:在开始升级之前,请确保备份所有重要的数据和配置文件。这有助于防止升级过程中发生意外情况导致数据丢失。
  • 分阶段升级:如果可能的话,建议分阶段升级。首先在开发环境中进行测试,确保一切正常后,再在生产环境中进行升级。这样可以减少风险并更好地控制升级过程。
  • 阅读官方文档:务必阅读新版本JDK的官方文档,了解新特性和变化。这有助于避免潜在的问题并更好地利用新版本的功能。
  • 社区支持:如果遇到困难,不要犹豫寻求社区的支持。有许多在线论坛和社区专门讨论Java开发问题。与其他开发人员交流经验和解决方案可以帮助解决问题。
    总之,从JDK 8升级到JDK 17是一个值得考虑的步骤,但也需要谨慎处理。通过仔细规划和充分准备,可以顺利完成升级并获得新版本JDK带来的好处。

2.1 安装JDK17

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

1

2

3

$ sudo su -

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

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

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

1

2

3

4

5

6

# 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

1

2

3

4

5

6

# 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版本由

1

2

3

4

5

6

7

8

9

<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>

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

1

2

3

4

5

6

7

8

9

<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>

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@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);

    };

}

改为以下代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@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相应的注解。即将:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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 {

    }

}

改为

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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参数实现的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<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

                    --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

根据错误提示,@RequestMapping注解不能添加在@FeignClient接口上了,改为通过在@FeignClient注解的path属性携带,即由:

1

2

3

4

@FeignClient(url = "127.0.0.1:8080", name = "service-feign")

@RequestMapping("/service")

public interface ServiceFeign {

}

改为:

1

2

3

@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版本依赖由:

1

2

3

4

5

<dependency>

    <groupId>com.github.xiaoymin</groupId>

    <artifactId>knife4j-spring-boot-starter</artifactId>

    <version>2.0.5</version>

</dependency>

改为:

1

2

3

4

5

<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的配置类内容由

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@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;

}

改为:

1

2

3

4

5

6

7

8

9

10

11

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

1

2

3

4

5

6

7

8

9

10

11

12

@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 cloudactuator的相关配置写在bootstrap.yml文件中,升级到spring boot 3之后actuator的端点(如/actuator/info等)无法访问,要想启动时系统读取bootstrap.yml中的配置,使bootstrap.yml文件中的配置生效,需要在项目的pom.xml文件中添加下面的依赖:

1

2

3

4

5

6

<dependencies>

    <dependency>

        <groupId>org.springframework.cloud</groupId>

        <artifactId>spring-cloud-starter-bootstrap</artifactId>

    </dependency>

</dependencies>

到此升级完成!

3 更进一步

之前项目中使用的GC收集器是CMS收集器,CMS收集器的调参非常繁琐,非常考验工程师的功底。

ZGC声称能保证8MB~16TB的堆内存范围内都能保证GC的停顿时间在毫秒(官方声称小于10ms,也有些文章表示实际只有1、2ms)级别,尤其是一般情况下只要设置几个基本的GC参数就能让GC收集器很好的工作了,简直是工程师的福音。

随着ZGC在JDK 15中正式GA,趁着升级JDK 17的机会立即把ZGC用起来。

设置几个基本的ZGC参数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

# 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"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01Byte空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值