从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
Lombok
从1.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> <aspectj.version>1.9.20</aspectj.version> </properties>
<dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <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> <version>${aspectj.version}</version> </dependency> <dependencies> <configuration> <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> <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> <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
...
|
生成的测试覆盖率报告是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
。
其他的一些迁移
如果有使用Lombok
的var
注解,需要把类文件开头的import lombok.var;
去掉。
如果有使用sun.misc.BASE64Decoder
或sun.misc.BASE64Encoder
这两个的代码需要改为使用java.util.Base64.Decoder
和java.util.Base64.Encoder
。
如果有使用反射,如getXXXField
和getXXXMethod
,来获取java.lang.reflect
和java.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);
|