多版本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:

Project Structure

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

java 复制代码
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代码:

Java Version Settings & Dependencies

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

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

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

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

Using Java 9 Features

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

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

java 复制代码
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():

java 复制代码
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文件中的类有一个非常特定的结构,因此我们将在编译器输出中使用这个结构。我们最终想要的东西看起来像:

java 复制代码
jar root
  - A.class
  - B.class
  - C.class
  - D.class
  - META-INF
     - versions
        - 9
           - A.class
           - B.class

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

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

java 复制代码
[project home]/artifacts/classes/production/root

java9模块需要特别注意:

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

java 复制代码
[project home]/artifacts/classes/production/root/META-INF/versions/9

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

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

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

Creating the JAR file

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

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

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

java 复制代码
[project home]/artifacts/jar

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

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

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

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

java 复制代码
Manifest-Version: 1.0
Main-Class: com.mechanitis.demo.multi.Main
Multi-Release: true

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

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

v

Running Under Different JVMs

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

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

Summary

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

相关推荐
王了了哇1 分钟前
【YOLO(txt)格式转VOC(xml)格式数据集】以及【制作VOC格式数据集 】
xml·python·yolo
蓝瓶电液6 分钟前
星际争霸小程序:用Java实现策略模式的星际大战
java·开发语言·策略模式
鸡鸭扣28 分钟前
leetcode hot100:解题思路大全
数据结构·python·算法·leetcode·力扣
无奇不有 不置可否29 分钟前
Java中的设计模式
java·开发语言·设计模式
冬瓜的编程笔记30 分钟前
【八股战神篇】Java集合高频面试题
java·面试
顾子茵1 小时前
游戏开发实战(一):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】
python·算法·游戏
martian6651 小时前
掌握Python编程:从C++/C#/Java开发者到AI与医学影像开发专家
开发语言·人工智能·python·dicom
※DX3906※1 小时前
小土堆pytorch--神经网路的基本骨架(nn.Module的使用)&卷积操作
人工智能·pytorch·python
ktkiko111 小时前
顶层设计-IM系统架构
java·开发语言·系统架构
Python×CATIA工业智造2 小时前
基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(一)
python·pycharm·catia二次开发