将Maven项目迁移到Java 11

18-09-02 banq
         

自2019年起,Oracle Java 8将不再获得免费的安全更新,现在是时候迁移到JDK 11了。

如果你想迁移到Java 11,但你的Maven项目仍然在Java 8上?你可能并不关心Java 9中引入的新模块系统(Jigsaw),只希望你的应用程序在最新的JDK版本上运行?那么本指南适合你,它包括我在将产品迁移到Java 11时学到的所有内容。

清理你的pom.xml文件#

在考虑升级Java版本之前,你应该做的第一件事就是清理pom.xml文件。

如果你的项目是一个多模块Maven项目,则需要建立一个父POM文件,其中有dependencyManagement pluginManagement选项,所有插件和依赖项都在这个文件中定义了,并且不会分布在多个POM文件中,这使得管理版本变得更加容易。

为了将项目迁移到最新的Java版本11,强烈建议尽可能多地更新插件和依赖项到最新的稳定版本,如果使用旧版本,许多插件(如编译器插件,surefire或failsafe)与Java 9不兼容,此外,许多库在不迁移到最新版本的情况下也不兼容。

确保你在主POM中配置了版本插件:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>versions-maven-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <generateBackupPoms>false</generateBackupPoms>
    </configuration>
</plugin>
<p>

此插件有助于查找模块的最新插件或依赖版本,打开终端并执行此命令以查找你必须更新的插件版本:

mvn versions:display-plugin-updates

你将看到项目中使用的插件列表,其中包含更新的版本,将所有这些插件更新到最新的稳定版本,更新插件版本后,请确保你的项目仍然可以编译并正常运行。

你可以从项目根目录中使用mvn -N ...,能在多模块项目的情况下检查你的父POM。

配置Java 11的插件

Java 11最重要的插件是编译器插件,surefire(用于单元测试)和failsafe (用于集成测试)。

为了编译Java 11项目,请将release配置添加到编译器插件中,这是一个新的编译器参数,用于替换source和target版本参数:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
        <release>11</release>
    </configuration>
</plugin>
<p>

另外,不要忘记将IDE项目SDK设置为相同的JDK版本。在Intellij IDEA中,转到模块设置 - >项目 - > SDK。

对于surefire和failsafe插件,我们添加了一个额外的参数,--illegal-access=permit以允许第三方库的所有反射访问:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <argLine>
            --illegal-access=permit
        </argLine>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.22.0</version>
    <configuration>
        <argLine>
            --illegal-access=permit
        </argLine>
    </configuration>
</plugin>
<p>

只有在依赖项大量使用反射时才需要这样做,如果你不确定是否需要这个,如果你的测试遇到麻烦,可以添加配置argLine选项。

在不添加这个选项情况下,当库包试图通过setAccessible(true)非法访问类时,你会看到下面的警告:

WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.reflection.CachedClass
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
<p>

请记住,你需要在启动应用程序时传递参数--illegal-access=permit。

更新依赖项#

如前所述,你可以做的最好的事情是将所有依赖项迁移到最新的稳定版本,以确保Java 11的一切正常。虽然许多较旧的依赖项可能正常工作但有一些依赖项,其中版本更新是必需的,例如各种不同的字节码增强工具:libaries javassist,cglib,asm或byte-buddy等。这些库通常作为依赖传递,因此请确保至少这些库是最新的。

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.23.1-GA</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.2.7</version>
</dependency>
<p>

下面命令有助于从模块中查找过时的依赖项版本:

mvn versions:display-dependency-updates

升级尽可能多的库到最新的稳定版本。如果由于项目中的兼容性问题导致你无法更新某些依赖项,但还是不要保留原样,改写它,有可能它在Java 11中运行良好。

现在是第一次使用JDK 11编译项目的好时机:

mvn clean test-compile compile

提示:你可以通过使用并行构建来加速多模块Maven项目,例如mvn -T 4 compile,这个命令可以在4个CPU内核上并行编译所有模块。

你可能会面临不同的编译器错误,例如ClassNotFoundException,每个项目都不同,所以我无法为你面临的每个问题提供解决方案。

本文的下面部分描述了升级JDK 11时必须解决的问题。

添加缺少的模块#

随着Java 9中Java模块系统(Jigsaw)的引入,Java标准库已被分成单独的模块,虽然大多数类没有任何变化,但有些类则会,你必须明确定义应用程序需要访问的其他模块,或者只需从Maven中央存储库添加这些模块即可。

java --list-modules这个命令能列出了所有可用的模块。

将我们的Web项目迁移到Java 11时,我们必须添加jaxb和javax.annotations,防止出现ClassNotFoundException,下面是我们已将以下库作为其他Maven依赖项添加到我们的POM中:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.4.0-b180725.0427</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.4.0-b180725.0644</version>
</dependency>
<p>

我们可以利用Java的–add-modulesJava参数向项目添加额外的JDK模块,注意不是通过Maven添加这些库的。

修复sun.*和com.sun.*等包的导入问题

虽然JDK的一些类已经转移到其他Java模块,但其他类就不能再用了,比如来自sun.*包的类以及来自的一些类com.sun.*,如果你的代码有链接到这些包中的类而导致编译器错误,则必须从代码中删除这些导入。

以下是我们在项目中需要解决的一些问题:

1. sun.misc.BASE64Encoder:这可以简单地替换为java.util.Base64.getEncoder(),这是自Java 8以来就可用的。

2. sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl:这个类在我们的代码库中被意外使用,可以简单地用接口类型替换java.lang.reflect.ParameterizedType。

3. sun.reflect.annotation.AnnotationParser:我们使用此类以编程方式创建注释实例,该类不再可访问,但可以用AnnotationFactoryHibernate Validator 替换。

4. com.sun.org.apache.xml.internal.utils.DefaultErrorHandler:我们已经用接口的自定义实现替换了这个类。

货币格式#

我们遇到了一个奇怪的案例,Locale.GERMANY其中包含语言环境的数字格式,例如,它让我们的一系列测试失败并出现了一个相当奇怪的断言错误:

java.lang.AssertionError:
Expected: is "9,80 €"
     but: was "9,80 €"
<p>

底层代码是使用NumberFormat.getCurrencyInstance(Locale.GERMANY)将数字格式化为德国货币格式,那么到底发生了什么?

Java的数字格式已被修改为使用非中断空格而不是数字和货币符号之间的正常空格。这种变化非常有意义,因为它可以防止在数字和货币符号之间的插入各种格式的换行符,更改测试中的字符串以使用不间断空格(在Mac OSX键盘上使用OPTION SPACE)修复了此问题。

Servlet容器#

使用Apache Tomcat运行Web应用程序时,至少需要Apache Tomcat 7.0.85或更高版本,否则Tomcat将无法在Java 9及更高版本上启动,你将看到以下错误:

/path/to/apache-tomcat-7.0.64/bin/catalina.sh run
-Djava.endorsed.dirs=/path/to/apache-tomcat-7.0.64/endorsed is not supported. Endorsed standards and standalone APIs
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
in modular form will be supported via the concept of upgradeable modules.
Disconnected from server
<p>

另外,不要忘记最终添加启动参数--illegal-access=permit到你的servlet容器中。

Migrate Maven Projects to Java 11