Multi-Release Jar Files – 多次发布的罐子文件

最后修改: 2019年 2月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java is constantly evolving and adding new features to the JDK. And, if we want to use those features in our APIs, then that can obligate downstream dependencies to upgrade their JDK version.

Java在不断发展,为JDK添加新的功能。而且,如果我们想在我们的API中使用这些功能,那么就会迫使下游的依赖者升级他们的JDK版本。

Sometimes, we are forced to wait on using new language features in order to remain compatible.

有时,我们被迫等待使用新的语言功能,以保持兼容。

In this tutorial, though, we’ll learn about Multi-Release JARs (MRJAR) and how they can simultaneously contain implementations compatible with disparate JDK versions.

在本教程中,我们将了解多版本JARs(MRJAR)以及它们如何同时包含与不同的JDK版本兼容的实现。

2. Simple Example

2.简单的例子

Let’s take a look at a utility class called DateHelper that has a method to check for leap years. Let’s assume that it was written using JDK 7 and built to run on JRE 7+:

让我们来看看一个名为DateHelper的实用类,它有一个方法来检查闰年。让我们假设它是用JDK 7编写的,并被构建为在JRE 7+上运行。

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 1 calendar API ");

        Calendar cal = Calendar.getInstance();
        cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(dateStr));
        int year = cal.get(Calendar.YEAR);

        return (new GregorianCalendar()).isLeapYear(year);
    }
}

The checkIfLeapYear method would be invoked from the main method of our test app:

checkIfLeapYear方法将从我们测试应用程序的main方法中调用。

public class App {
    public static void main(String[] args) throws Exception {
        String dateToCheck = args[0];
        boolean isLeapYear = DateHelper.checkIfLeapYear(dateToCheck);
        logger.info("Date given " + dateToCheck + " is leap year: " + isLeapYear);
    }
}

Let’s fast forward to today.

让我们快进到今天。

We know that Java 8 has a more concise way to parse the date. So, we’d like to take advantage of this and rewrite our logic. For this, we need to switch to JDK 8+. However, that would mean our module would stop working on JRE 7 for which it was originally written.

我们知道,Java 8 有一种更加简洁的方式来解析日期。因此,我们想利用这一点并重写我们的逻辑。为此,我们需要切换到JDK 8+。然而,这意味着我们的模块将停止在 JRE 7 上工作,而它最初就是为该模块编写的。

And we don’t want that to happen unless absolutely required.

除非绝对需要,否则我们不希望发生这种情况。

3. Multi-Release Jar Files

3.多次发布的Jar文件

The solution in Java 9 is to leave the original class untouched and instead create a new version using the new JDK and package them together. At runtime, the JVM (version 9 or above) will call any one of these two versions giving more preference to the highest version that the JVM supports.

Java 9中的解决方案是:不动原来的类,而是使用新的JDK创建一个新版本,并将它们打包在一起。在运行时,JVM(版本9或以上)将调用这两个版本中的任何一个更倾向于JVM支持的最高版本

For example, if an MRJAR contains Java version 7 (default), 9 and 10 of the same class, then JVM 10+ would execute version 10, and JVM 9 would execute version 9. In both cases, the default version is not executed as a more appropriate version exists for that JVM.

例如,如果一个MRJAR包含同一类别的Java版本7(默认)、9和10,那么JVM 10+将执行版本10,而JVM 9将执行版本9。在这两种情况下,默认版本不会被执行,因为对于该JVM来说,存在一个更合适的版本。

Note that the public definitions of the new version of the class should exactly match the original version. In other words, we’re not allowed to add any new public APIs exclusive to a new version.

注意,新版本的类的公共定义应该与原版本完全一致。换句话说,我们不允许在新版本中加入任何新的公共API,这是对新版本的专属。

4. Folder Structure

4.文件夹结构

As classes in Java map directly to files by their names, creating a new version of DateHelper in the same location is not possible. Hence, we need to create them in a separate folder.

由于Java中的类通过名称直接映射到文件,在同一位置创建新版本的DateHelper是不可能的。因此,我们需要在一个单独的文件夹中创建它们。

Let us start by creating a folder java9 at the same level as java. After that, let’s clone the DateHelper.java file retaining its package folder structure and place it in java9:

让我们首先创建一个与java同级的文件夹java9。之后,让我们克隆DateHelper.java文件,保留其包的文件夹结构,并将其放在java9:中。

src/
    main/
        java/
            com/
                baeldung/
                    multireleaseapp/
                        App.java
                        DateHelper.java
        java9/
            com/
                baeldung/
                    multireleaseapp/
                        DateHelper.java

Some IDEs that don’t yet support MRJARs may throw errors for duplicate DateHelper.java classes.

一些尚不支持MRJARs的IDE可能会对重复的DateHelper.java类产生错误。

We’ll take up how to integrate this with build tools like Maven in another tutorial. For now, let’s just focus on the fundamentals.

我们将在另一篇教程中讨论如何将其与Maven等构建工具集成。现在,让我们只关注基本原理。

5. Code Changes

5.法规修改

Let’s rewrite the logic of the java9 cloned class:

让我们重写一下java9 克隆类的逻辑。

public class DateHelper {
    public static boolean checkIfLeapYear(String dateStr) throws Exception {
        logger.info("Checking for leap year using Java 9 Date Api");
        return LocalDate.parse(dateStr).isLeapYear();
    }
}

Note here that we’re not making any changes to the public method signatures of the cloned class but only changing the inner logic. At the same time, we’re not adding any new public methods.

请注意,我们没有对克隆类的公共方法签名进行任何修改,只是改变了内部逻辑。同时,我们也没有添加任何新的公共方法。

This is very important because the jar creation will fail if these two rules are not followed.

这一点非常重要,因为如果不遵守这两条规则,jar的创建将会失败。

6. Cross-Compilation in Java

6.Java中的交叉编译

Cross-compilation is the feature in Java that can compile files for running on earlier versions. This means there is no need for us to install separate JDK versions.

交叉编译是Java中的一项功能,它可以将文件编译到早期版本上运行。这意味着我们不需要安装单独的JDK版本。

Let’s compile our classes using JDK 9 or above.

让我们使用JDK 9或以上版本编译我们的类。

Firstly, compile the old code for the Java 7 platform:

首先,为Java 7平台编译旧代码。

javac --release 7 -d classes src\main\java\com\baeldung\multireleaseapp\*.java

Secondly, compile the new code for the Java 9 platform:

第二,为Java 9平台编译新代码。

javac --release 9 -d classes-9 src\main\java9\com\baeldung\multireleaseapp\*.java

The release option is used to indicate the version of Java compiler and target JRE.

release选项用于指示Java编译器和目标JRE的版本。

7. Creating the MRJAR

7.创建MRJAR

Finally, create the MRJAR file using version 9+:

最后,使用9+版本创建MRJAR文件。

jar --create --file target/mrjar.jar --main-class com.baeldung.multireleaseapp.App
  -C classes . --release 9 -C classes-9 .

The release option followed by a folder name makes the contents of that folder to be packaged inside the jar file under the version number value:

release选项后面跟着一个文件夹名称,使得该文件夹的内容被打包在版本号值下的jar文件中:

com/
    baeldung/
        multireleaseapp/
            App.class
            DateHelper.class
META-INF/
    versions/
        9/
            com/
                baeldung/
                    multireleaseapp/
                        DateHelper.class
    MANIFEST.MF

The MANIFEST.MF file has the property set to let the JVM know that this is an MRJAR file:

MANIFEST.MF文件设置了属性,让JVM知道这是一个MRJAR文件。

Multi-Release: true

Consequently, the JVM loads the appropriate class at runtime.

因此,JVM在运行时加载适当的类。

Older JVMs ignore the new property that indicates this is an MRJAR file and treat it as a normal JAR file.

旧的JVM忽略了表明这是一个MRJAR文件的新属性,而把它当作一个普通的JAR文件。

8. Testing

8.测试

Finally, let’s test our jar against Java 7 or 8:

最后,让我们针对Java 7或8测试一下我们的jar。

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 1 calendar API 
Date given 2012-09-22 is leap year: true

And then, let’s test the jar again against Java 9 or later:

然后,让我们再次用Java 9或更高版本测试这个jar。

> java -jar target/mrjar.jar "2012-09-22"
Checking for leap year using Java 9 Date Api
Date given 2012-09-22 is leap year: true

9. Conclusion

9.结语

In this article, we’ve seen how to create a multi-release jar file using a simple example.

在这篇文章中,我们已经看到了如何使用一个简单的例子来创建一个多版本的jar文件。

As always, the codebase for multi-release-app is available over on GitHub.

一如既往,多发布应用的代码库可在GitHub上获取。