zjjfly's blog

Java,Clojure,Scala...

0%

从Java 8迁移至Java 17

从Java 8迁移至Java 17

升级Java和Maven

首先安装Java 17,并把JAVA_HOME改成新版本的安装路径。还需要升级Maven版本,尽量用最新的版本,因为旧版本并不支持Java 17。在IDE修改项目使用的JDK和Maven。然后,在pom.xml中,加入或修改下面的配置(如果有配置<java.version>,要删除):

1
2
3
4
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>

如果有显式地配置compiler plugin,则也需要修改:

1
2
3
4
5
6
7
<plugin>    
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>

升级Lombok

Lombok1.18.22开始支持Java 17,所以pom.xml中如果有Lombok的依赖,需要检查是否大于等于这个版本。

升级ApsectJ版本

如果项目使用AspectJ进行AOT,相关的Maven插件要换成另一个,因为最广为使用的Aspectj插件暂时不支持Java 17。修改后的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<project>
<properties>
<!-- Your favourite AspectJ version -->
<aspectj.version>1.9.20</aspectj.version>
</properties>

<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<!-- AspectJ runtime version, in sync with compiler -->
<version>${aspectj.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.13.1</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<!-- AspectJ compiler version, in sync with runtime -->
<version>${aspectj.version}</version>
</dependency>
<dependencies>
<configuration>
<!-- Your favourite Java source/target version -->
<complianceLevel>17</complianceLevel>
</configuration>
</plugin>
</plugins>
<build>
</project>

升级JUnit版本

使用最新的JUnit版本,需要修改pom.xml中JUnit相关的依赖配置,修改后的结果如下:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>

Java 9的模块化对反射的使用做了限制,需要在maven-surefile-plugin加上命令行参数来避免报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- 使用最新版本的surefire插件 -->
<version>3.0.0-M7</version>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.xml/jdk.xml.internal=ALL-UNNAMED
--add-opens java.base/java.time=ALL-UNNAMED
--add-opens java.base/java.time.format=ALL-UNNAMED
</argLine>
</configuration>
</plugin>

从PowerMock迁移到Mockito-inline

目前PowerMock无法兼容Mockito的4.x版本(Spring Boot 2.7.6的test starter就是使用的这个版本),虽然已经有Pull Request,代码也已经被合入,但是PowerMock最新的版本是两年前发布的,无法确定新版本什么时候发布。虽然可以自己在构建一个可用的版本,但这显然不够正规。更好的方案是使用mockito-inline。它几乎可以完全可以替换PowerMock,详细用法见之前的这篇blog

首先删除所有PowerMock的依赖,然后加入mockito-inline的依赖即可:

1
2
3
4
5
6
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.8.0</version>
<scope>test</scope>
</dependency>

从OpenClover迁移到JaCoCo

如果使用了OpenClover来生成代码覆盖率报告(为了兼容PowerMock),则需要切换为JaCoCo,因为OpenClover不支持Java 17,而JaCoCo的最新版是支持的,它也可以很好的和mockito-inline一起工作。

首先删除原来的OpenClover的依赖和插件配置,加入Jacoco的依赖和插件配置并更新surefire的插件配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<properties>
<jacoco.version>0.8.8</jacoco.version>
</properties>

<dependencies>
...
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>${jacoco.version}</version>
<classifier>runtime</classifier>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-instrument</id>
<goals>
<goal>instrument</goal>
</goals>
<!-- 下面这一行仅针对使用Aspect AOT的情况,因为需要保证AspectJ先对测试代码进行编译,再使用Jacoco对字节码进行instrument,否则会报错 -->
<phase>process-test-classes</phase>
</execution>
<execution>
<id>default-restore-instrumented-classes</id>
<goals>
<goal>restore-instrumented-classes</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/coverage.exec</dataFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.xml/jdk.xml.internal=ALL-UNNAMED
--add-opens java.base/java.time=ALL-UNNAMED
--add-opens java.base/java.time.format=ALL-UNNAMED
</argLine>
<systemPropertyVariables>
<jacoco-agent.destfile>${project.build.directory}/coverage.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plugin>
<</plugins>
</build>

修改sonar-project.properties以使用JaCoCo生成的覆盖率报告:

1
2
3
4
5
6
...
sonar.core.codeCoveragePlugin=jacoco
sonar.junit.reportsPath=target/surefire-reports
sonar.jacoco.reportPath=target/coverage.exec
#请删除"sonar.clover.reportPath=target/site/clover/clover.xml"这一行
...

生成的测试覆盖率报告是target/coverage.exec

Java11删除Java EE模块引起的问题

Java 9引入了模块化的同时,把下面这些模块标记为过时并将在未来版本删除:

  • java.xml.ws (JAX-WS, plus the related technologies SAAJ and Web Services Metadata)

  • java.xml.bind (JAXB)

  • java.activation (JAF)

  • java.xml.ws.annotation (Common Annotations)

  • java.corba (CORBA)

  • java.transaction (JTA)

  • java.se.ee (Aggregator module for the six modules above)

  • jdk.xml.ws (Tools for JAX-WS)

  • jdk.xml.bind (Tools for JAXB)

而到了Java 11,上述的九个模块正式被删除并作为独立的库继续存在。所以,如果有代码或第三方库依赖其中的类,则需要显式地在pom.xml中加入相应的依赖。在JEP-320有详细的描述以及对应的依赖。

xjar

如果使用xjar对JAR进行加密,需要在运行xjar的时候加上参数--add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED

其他的一些迁移

  • 如果有使用Lombokvar注解,需要把类文件开头的import lombok.var;去掉。

  • 如果有使用sun.misc.BASE64Decodersun.misc.BASE64Encoder这两个的代码需要改为使用java.util.Base64.Decoderjava.util.Base64.Encoder

  • 如果有使用反射,如getXXXFieldgetXXXMethod,来获取java.lang.reflectjava.lang.invoke 里的类的私有字段或方法,会无法找到,这是Java 12里加入的修改。以把java.lang.reflection.Field对象的modifers改为非final的代码为例(一般用于把对象的某个final字段改成非final字段),原来的代码需要改成下面这样的:

1
2
3
4
Field bacnetEndpointField = BacnetConsumerStrategy.class.getDeclaredField("bacnetEndpoint");
Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);
modifiers.set(bacnetEndpointField,bacnetEndpointField.getModifiers() & ~Modifier.FINAL);