多版本jdk共享jar

多版本jdk共享jar

Java 9的一个有趣特性是支持多版本JAR文件。这是什么意思?过去,库开发人员在支持新版本的Java时有三种选择:

  1. 提供两个(或更多)不同的JAR文件,每个文件对应他们想要支持的Java版本。它们的版本号可能是“1.2-java-5”和“1.2-java-1.3”。
  2. 将每个发行版绑定到特定的Java版本,迫使用户要么升级他们的Java版本,要么被困在旧版本的库中。例如“5.0版本以后需要Java 8”。
  3. 坚持为用户提供最小公分母的版本。对于许多库开发人员来说,这意味着他们仍然是针对Java 6进行编译的,并且在几乎所有的用户都已经迁移到Java 8之前,他们无法迁移到使用Java 8的特性,比如lambdas和streams。

对于库开发人员或其用户来说,这些方法都不是特别有趣。它们要么涉及大量工作,要么疏远/混淆用户,要么库不能利用新特性(因此也不能为用户提供太多的动机来升级他们的Java版本)。

从Java 9开始,有一个替代方案。现在,库开发人员可以发布一个JAR文件:

  • 如果您在Java 9上运行它,则使用Java 9的特性和功能
  • 如果在Java 9之前的版本上运行它,则得到的是Java 9之前的实现。

这适用于Java 9以后的版本——所以这些多版本JAR文件将支持Java 9版本、Java 10(或18.3)、Java 11、Java 12版本等等,但Java 9之前的任何版本都被归为“pre-Java 9”。这有点让人难过,因为如果你运行的是Java 8, Java 8显然有一些不错的特性,但Java 9之前对库的支持可能会以6为目标,就像许多库一样。当然,这种分离的原因是Java 8本身无法决定在运行多版本JAR文件时做什么不同的事情,因为这些功能只有在Java 9中才可用。

在这篇博文中,我将展示如何在IntelliJ IDEA中创建一个多版本的JAR文件。我强烈建议您不要使用IDE来创建应用程序的生产就绪构建,我希望大多数人使用Maven, Gradle, Ant或其他构建工具。然而,我想尝试多版本JAR文件,并设法使用IntelliJ IDEA构建它们,并希望展示这个过程可以帮助人们理解如何构建多版本JAR文件以及它们是如何工作的。

The Example

我将创建一个非常简单的应用程序,它只输出当前堆栈跟踪(稍后您将看到我为什么选择这个示例)。我的项目包括一个Main类,一个定义我可能对栈感兴趣的接口,StackInfo,以及这个接口的实现,StackParser:

01BasicStructure

Project Structure

如果你看一下规范,你会发现你需要的是一个输出结构,看起来像这样:

jar root
  - A.class
  - B.class
  - C.class
  - D.class
  - META-INF
     - versions
        - 9
           - A.class
           - B.class

基本上,像往常一样,在根目录中有一个包含应用程序中所有类的标准JAR文件,在META-INF中有一个附加的“版本”文件夹,其中包含每个附加支持的Java版本(在这种情况下,只有Java 9)的特定实现。这个“9”文件夹只需要包含具有特定Java 9功能的类的类文件。如果一个类不在其中(例如C.class),则使用默认版本。

如果我想要我的应用程序的部分被编译针对Java 9和“默认”应用程序编译针对Java 8,我可以在IntelliJ IDEA中做到这一点的一种方法是设置一个不同的IntelliJ IDEA模块只包含Java 9代码:

02Java9Module-2

Java Version Settings & Dependencies

在我的项目结构中,我将把Java 8设置为默认值,因为这是我希望在正常情况下对应用程序进行编译的标准。

03Java8Default

如果我查看根项目的设置,我应该会看到它使用了默认的SDK Java 8。

04RootJava8

现在,我需要进入java9模块,并确保将其设置为针对JDK9进行编译。

05java9JDK9

我还在这个java9模块中添加了根模块的依赖项。这样做的原因是根项目包含所有的项目代码,而java9模块只包含需要针对Java 9编译的类。这些类可能需要引用应用程序中的其他类,因此我们将依赖根项目来访问这些其他类。

Using Java 9 Features

现在我创建StackParser的Java 9实现。

06Java9StackParser

现在你知道我为什么选择这个例子了——在Java 9中有一个新的StackWalking API,它使获取堆栈信息变得更容易(通常也更有效)。多版本JAR文件的规范规定“每个版本的库都应该提供相同的API”——这意味着实际上您应该只使用Java 9来实现细节,而不是为用户提供不同的API。使用Java 9特性的事实对用户来说是不可见的。库开发人员是否会遵循这一点还有待观察。但是对于我们的例子,我们将在Java 8和Java 8版本中使用完全相同的API(两个StackParser类都实现了StackInfo),但是Java 9版本在其实现中使用了Java 9的特性。

public class StackParser implements StackInfo {

    @Override
    public String getStackCount() {
        return "Java 9: " + StackWalker.getInstance()
                                       .walk(Stream::count);
    }

    @Override
    public String getStack() {
        return StackWalker.getInstance()
                          .walk(frames -> frames.map(Object::toString)
                                                .collect(joining("\n")));
    }
}

相比之下,我们的Java 8版本使用了currentThread().getStackTrace():

public class StackParser implements StackInfo {
    @Override
    public String getStackCount() {
        return "Java 8: " + Thread.currentThread()
                                  .getStackTrace().length;
    }

    @Override
    public String getStack() {
        return Arrays.stream(Thread.currentThread()
                                   .getStackTrace())
                     .map(element -> element.toString())
                     .collect(Collectors.joining("\n"));
    }
}

请注意,这两个类的名称相同,并且位于同一个包中。

Compiling

请记住,JAR文件中的类有一个非常特定的结构,因此我们将在编译器输出中使用这个结构。我们最终想要的东西看起来像:

jar root
  - A.class
  - B.class
  - C.class
  - D.class
  - META-INF
     - versions
        - 9
           - A.class
           - B.class

我们的root项目可以被编译到任何我们喜欢的地方,只要我们知道它在哪里。我有我的项目设置如下:

07RootOutput

我的根模块编译输出路径设置为

[project home]/artifacts/classes/production/root

java9模块需要特别注意:

08Java9Output

我将java9模块输出路径设置为

[project home]/artifacts/classes/production/root/META-INF/versions/9

现在,当您构建整个项目时,所有内容都应该按照您的期望编译,并且您应该在artifacts目录中看到输出:

09ProjectCompileOutput

当我在根目录下打开StackParser类时,我看到它是用Java 8编译的:

10CompileWith8

如果我打开9文件夹中的那个,我可以看到它是用Java 9编译的。

Creating the JAR file

接下来,我们将告诉IntelliJ IDEA如何组装JAR文件。在Project Structure对话框的artifacts部分,我们将创建一个新的工件。我点击工件窗口顶部的“+”并选择JAR -> empty。

11NewJAR

我要将名称更改为“multi-release”,然后右键单击可用元素中的“root”,选择“Put Into Output root”。

12PutRootIntoOutput

我对java9模块也做了同样的事情。我将更改JAR文件的目标文件夹,因为我希望它位于不同的位置,但这对该过程并不重要(只要您记得它将在哪里输出)。我把输出路径设为

[project home]/artifacts/jar

然后我点击multi-release.jar并按下“Create Manifest”按钮。

13CreateManifest

我将选择根模块作为这个的位置([project home]/root), IntelliJ IDEA在这里创建一个带有MANIFEST的META-INF文件夹。MF文件。

现在我可以按OK保存所有这些设置。

接下来,我要进入MANIFEST.MF文件,并做了一些改变:

Manifest-Version: 1.0
Main-Class: com.mechanitis.demo.multi.Main
Multi-Release: true

最后一行是最重要的一行。

最后,在Build菜单中,我选择Build Artifacts…并在multi-release.jar下选择“Build”。现在我应该在我选择的输出目录中看到 JAR

14JarFileOutputv

Running Under Different JVMs

最后,让我们看看这个JAR文件的实际情况。首先,我设置了一个运行Java 8的终端。当我从这里运行jar文件时,我得到Java 8实现:

15RnWithJava8

然后,在用Java 9设置的第二个终端中,运行完全相同的JAR文件,就得到了Java 9的实现。

15RnWithJava9

Summary

在这篇博文中,我们讨论了:什么是多版本JAR文件,为什么它可能有用;如何创建一个IntelliJ IDEA项目,它可以有Java 8和Java 9实现相同的功能;如何使用IntelliJ IDEA创建一个多版本的JAR文件;当您使用不同的Java版本运行多版本JAR文件时会发生什么。

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值