文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

Java 9附带了一个对于库作者非常有用的新功能:多版本JAR (JEP 238)。

多版本JAR(MR JAR)可能包含同一类的多个变体,每个变体都针对特定的Java版本。 在运行时,类的正确变体将被自动加载,这取决于所使用的Java版本。

这允许库作者在早期利用新的Java版本,同时保持与旧版本的兼容性。 假如你的库对变量执行原子比较和设置(compare-and-set)操作,那么你现在可以使用sun.misc.Unsafe类。 由于Unsafe从未用于JDK本身以外的用途,因此Java 9提供了一种支持的替代方法,用于以f var handle形式的CAS逻辑。 通过将库提供为MR JAR,当你在Java 9上运行时,可以受益于var handle,而在旧平台上运行时保持Unsafe

第 1 段(可获 1.9 积分)

在下面,我们将讨论如何使用Apache Maven创建MR JAR。

多版本JAR的结构

多版本JAR包含几个类文件的树。 主树位于JAR的根,而版本特定的树位于 META-INF/versions下,例如。 像这样:

JAR root
- Foo.class
- Bar.class
+ META-INF
   - MANIFEST.MF
   + versions
      + 9
         - Bar.class

这里,来自JAR根的Foo和Bar类将用于不识别MR JAR(即Java 8和更早版本)的Java运行时,而来自JAR根的Foo和来自META-INF / versions / 9的Bar将是 在Java 9和更高版本中使用。 JAR清单(manifest )必须包含一个条目Multi-Release:true,以指示JAR是MR JAR。

第 2 段(可获 1.4 积分)

示例:获取当前进程的Id

举个例子,假设我们有一个库,它定义了一个类,用于提供它运行的进程(PID)的id。PID应该由一个描述符表示,该描述符包括实际的PID和描述PID提供者的String:

src/main/java/com/example/ProcessIdDescriptor.java

package com.example;

public class ProcessIdDescriptor {

    private final long pid;
    private final String providerName;

    // constructor, getters ...
}

直到Java 8,都没有一种简单的方法来获取正在运行的进程的id。 一个相当简单的方法是解析 RuntimeMXBean#getName() 的返回值,它在OpenJDK / Oracle JDK中实现为“pid @ hostname”。 虽然这种行为不能保证在实现之间可移植,让我们将它作为我们默认 ProcessIdProvider的基础:

第 3 段(可获 1.48 积分)

src/main/java/com/example/ProcessIdProvider.java

package com.example;

public class ProcessIdProvider {

    public ProcessIdDescriptor getPid() {
        String vmName = ManagementFactory.getRuntimeMXBean().getName();
        long pid = Long.parseLong( vmName.split( "@" )[0] );
        return new ProcessIdDescriptor( pid, "RuntimeMXBean" );
    }
}

再让我们创建一个简单的主类来显示PID和获取该PID的provider:

src/main/java/com/example/Main.java

package com.example;

public class Main {

    public static void main(String[] args) {
        ProcessIdDescriptor pid = new ProcessIdProvider().getPid();

        System.out.println( "PID: " + pid.getPid() );
        System.out.println( "Provider: " + pid.getProviderName() );
    }
}
第 4 段(可获 0.41 积分)

请注意目前为止创建的源文件位于常规src/main/java源目录中。

现在让我们基于Java 9的新的 ProcessHandle API创建另一个 ProcessIdDescriptor  的变体,它最终提供了一种获取当前PID的便携式方法。 此源文件位于另一个源目录src/main/java9中

src/main/java9/com/example/ProcessIdProvider.java

package com.example;

public class ProcessIdProvider {

    public ProcessIdDescriptor getPid() {
        long pid = ProcessHandle.current().getPid();
        return new ProcessIdDescriptor( pid, "ProcessHandle" );
    }
}
第 5 段(可获 0.78 积分)

设置构建

有了所有的源文件,现在是配置Maven的时候了,以便创建一个MR JAR。

需要三个步骤。 第一件事是在  src/main/java9 下编译额外的Java 9源代码。 我希望我可以简单地设置另一个Maven编译器插件的execution,但我找不到一个只编译src/main/java9,而不是再次编译来自 src/main/java的文件的方法。

作为一种变通的办法,Maven Antrun插件可用于为Java 9特定源配置第二个javac运行:

第 6 段(可获 1.35 积分)

pom.xml文件

...
<properties>
    <java9.sourceDirectory>${project.basedir}/src/main/java9</java9.sourceDirectory>
    <java9.build.outputDirectory>${project.build.directory}/classes-java9</java9.build.outputDirectory>
</properties>
...
<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <id>compile-java9</id>
                    <phase>compile</phase>
                    <configuration>
                        <tasks>
                            <mkdir dir="${java9.build.outputDirectory}" />
                            <javac srcdir="${java9.sourceDirectory}" destdir="${java9.build.outputDirectory}"
                                classpath="${project.build.outputDirectory}" includeantruntime="false" />
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        ...
    </plugins>
    ...
</build>
...
第 7 段(可获 0.03 积分)

它使用 target/classes 目录(包含由默认编译产生的类文件)作为类路径,允许引用我们的MR JAR支持的所有Java版本的常见类,例如 ProcessIdDescriptor。 编译的类放入target/classes-java9

下一步是将编译的Java 9类复制到 target/classes 中,以便以后将它们放到生成的JAR中的正确位置。 可使用Maven资源插件这样做:

pom.xml

...
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-resources</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
                <resources>
                    <resource>
                        <directory>${java9.build.outputDirectory}</directory>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>
...
第 8 段(可获 1.01 积分)

这将把Java 9类文件从target/classes-java9复制到target/classes/META-INF/versions/9

最后,需要配置Maven JAR插件,以便将Multi-Release条目添加到清单文件中:

pom.xml

...
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Multi-Release>true</Multi-Release>
                <Main-Class>com.example.Main</Main-Class>
            </manifestEntries>
        </archive>
        <finalName>mr-jar-demo.jar</finalName>
    </configuration>
</plugin>
...
第 9 段(可获 0.46 积分)

就这样,我们为构建一个多版本的JAR将所有东西放在一起。 通过mvn clean package(使用Java 9)触发构建以在目标 目录中创建JAR。

为了看看JAR内容是否正确,通过 jar -tf target/mr-jar-demo.jar列出其内容。 你应该看到以下内容:

...
com/example/Main.class
com/example/ProcessIdDescriptor.class
com/example/ProcessIdProvider.class
META-INF/versions/9/com/example/ProcessIdProvider.class
...

最后,让我们通过 java -jar target/mr-jar-demo.jar来执行JAR并检查它的输出。 使用Java 8或更早版本时,你将看到以下内容:

第 10 段(可获 0.89 积分)
PID: <some pid>
Provider: RuntimeMXBean

而在Java 9上,它将是这样:

PID: <some pid>
Provider: ProcessHandle

也就是说来自JAR根的ProcessIdProvider类将在Java 8和更早版本上使用,而在Java 9上将使用META-INF/versions/9中的那一个。

结论

虽然javac,jar,java和其他JDK工具已经支持多版本JAR,但是像Maven这样的构建工具仍然需要迎头赶上。 幸运的是,它暂时可以使用一些插件,但我希望Maven等其他工具在不久的将来可以为创建MR JAR提供适当的开箱即用的支持。

第 11 段(可获 1.08 积分)

其他人也在考虑创建MR JAR。 例如。 请查看我的同事David M. Lloyd的这篇文章。 David为Java 9特定的类使用单独的Maven项目,然后使用Maven依赖性插件将其复制回主项目。 就个人而言,我更喜欢将所有的来源放在一个项目中,因为我发现这样做更简单,或许是我的一个小怪癖。 具体来说,如果你同时将src/main/java 和src/main/java9,配置为你的IDE中的源目录,你会得到一个关于重复的ProcessIdProvider类的错误。 这可以被忽略(如果你不需要用它,你也可以从IDE中删除src/main/java9作为源目录),但它可能有些烦人。

第 12 段(可获 1.7 积分)

可以考虑在另一个包中使用Java 9类,例如。 java9.com.example然后在构建项目时使用Maven shade 插件将它们重定位到com.example,虽然这看起来为了一个很小的收益付出了相当多的努力。 最终,如果IDE在单个项目中为不同源和目标目录也添加了MR JAR的支持和多个编译,这也是可取的。

在下面的评论部分中欢迎任何关于这个创建MR JAR的方法或其他方法的反馈。 这个博客的完整源代码可以在GitHub上找到。

第 13 段(可获 1.23 积分)

文章评论