还在因 JDK 兼容问题发不同 JAR 包做兼容?MRJAR 了解一下?

815 阅读4分钟

工匠若水可能会迟到,但是从来不会缺席,最终还是觉得将自己的云笔记分享出来吧 ~

背景

Java 9 版本中增强了Jar 包多版本字节码文件格式支持,也就是说在同一个 Jar 包中我们可以包含多个 Java 版本的 class 文件,这样就能做到 Jar 包升级到新的 Java 版本(新特性 API 使用)时不用强迫使用方为了使用新 Jar 包而升级自己的业务模块 Java 版本,也不用针对不同最低支持 Java 版本提供不同的 Jar,真正的做到了一个 Jar 包兼容所有的目的。这样的 Jar 称为 MRJAR。

MRJAR 中的代码包含在不同版本 JDK 下编译的 class 文件。譬如使用 JDK9 编译的类可以调用 JDK9 提供的 API,而使用 JDK8 编译的类可以调用 JDK8 提供的 API,只要保证他们在 MRJAR 中的包名、类名、类对外调用都一致即可。

MRJAR 规则

一般典型情况下的 JAR 包内部包含 class 文件和一个属性META-INF/MANIFEST.MF文件,如下:

- jar-root
  - A.class
  - B.class
  - C.class
- META-INF
  - MANIFEST.MF

MRJAR 扩展自 JAR 的目录结构,扩展了META-INF目录以便存储特定 JDK 版本的 class 文件,META-INF目录包含一个版本子目录,其中可能包含许多子目录,每个目录的命名需要与 JDK 主要版本相同。譬如,对特定于 JDK9 的 class 可以放在META-INF/versions/9目录下,对特定于 JDK10 的 class可以放在META-INF/versions/10目录下等。所以一般典型情况下的 MRJAR 包内部大致如下:

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

上面的例子中在不同 JDK 环境下运行表现如下:

  • 如果这个 MRJAR 在不支持 MRJAR 的 JDK 环境(譬如 JDK8)下使用,则会被自动兼容当做普通 JAR 使用,即META-INF/versions/目录下都被忽略,直接使用了 root 下的 class,所以无法访问 D。
  • 如果这个 MRJAR 在 JDK9 中使用,则只有 A、B、C、D 这几个 class 可以使用,且这里的 A、D 用的是META-INF/versions/9/下面的 class,其他用的 root 目录下的 class。
  • 对于 JDK10 来说,原理类同上面 JDK9,这里不多解释。只是说当我们在 JDK10 环境使用 C 类时搜索的顺序是先搜索META-INF/versions/10/下是否存在 C,如果不存在则搜索META-INF/versions/9/下是否存在 C,如果不存在则搜索根 root 目录下是否存在,这个查找顺序一定要明白。
  • 对 JDK11 来说,因为不存在META-INF/versions/11/,所以依次在低于自己的版本中搜索,在这里也就等同于在 JDK10 下被命中的类。

制作 MRJAR

JDK9 对生成 JAR 包的各种命令和工具都升级为支持 MRJAR,所以制作 MRJAR 最好直接使用 JDK9 开始的环境,其 jar 命令新增了一个参数为--release,语法如下:

//N代表一个JDK主版本,如JDK9中的9,且N值必须大于等于9;
//所有在--release N之后的文件都会被添加到MRJAR的META-INF/versions/N目录下;

jar <options> --release N <other-options>

假设现在我们准备好了一套 JDK8 的类和一套 JDK12 的类和编译成 class 的产物,如下:

//JDK8的源文件,编译产物目录假设为 jdk8/build/classes/
package cn.yan.mrjar;

public class JarUtil {
    public String func() {
        //JDK8 API实现功能
    }
}

//JDK12的源文件,编译产物目录假设为 jdk12/build/classes/
package cn.yan.mrjar;

public class JarUtil {
    public String func() {
        //JDK12 API实现功能
    }
}

接着我们对上面不同版本生成一个 MRJAR,如下命令:

jar --create --verbose --file test-out.jar
-C jdk8/build/classes .
--release 12 -C jdk12/build/classes .

这样就生成了一个 MRJAR,JDK12 的 JarUtil 类会被放进META-INF/versions/12/目录下,JDK8 的会被放进 root 根目录下。此时的 MRJAR 解压结构如下:

- jar-root
  - cn
    - yan
      - mrjar
        - JarUtil.class
- META-INF
  - MANIFEST.MF
  - versions
    - 9
      - cn
        - yan
          - mrjar
            - JarUtil.class

这样就能将上面制作好的的 MRJAR 在不同 JDK 环境下使用了,你说棒不棒,感觉有用来个在看的同时发给你的好基友呀。

【工匠若水 未经允许严禁转载,请尊重作者劳动成果】