如何为命名模块指定 java Manifest Add-Exports

How to specify java Manifest Add-Exports for named modules

我有一个 named 模块,它在编译时使用了一些 --add-exports。 我如何在不在 运行 命令行中手动指定的情况下暗示该指令?

命名模块的编译

javac --add-exports java.base/sun.security.x509=mymodule --module-source-path src -d bin --module mymodule

打包为 JAR

jar -c --file=mymodule.jar --main-class=mymodule.Main -C bin/mymodule

运行

java --module-path . --module mymodule/mymodule.Main

原因:

cannot access class sun.security.x509.AlgorithmId (in module java.base) because module java.base does not export sun.security.x509 to module mymodule

cannot access class sun.security.x509.AlgorithmId (in module java.base) because module java.base does not export sun.security.x509 to unnamed module @0x53bd815b

http://openjdk.java.net/jeps/261 状态:

A module/package pair in the value of an Add-Exports attribute has the same meaning as the command-line option --add-exports module/package=ALL-UNNAMED

如何指定导出到 ALL-UNNAMED 而不是 mymodule?如何让它发挥作用?

如果我的模块不是主要 class 而是一个使用更深的库,那么我必须为我有时可能直接从命令行使用的所有可能模块指定所有导出?

目前看来(JRE 17),这是不可能的。

仅在 2 种情况下执行导出:

  1. 当以命令行参数--add-exports启动时(很明显)

  2. 当使用 -jar 选项启动程序时,它会检查 MANIFEST.MF 是否有 Add-Exports 指令。到目前为止,这是 唯一 例检查清单的情况。

这意味着以java -cp my.jar my.Main开头将不会检查jar文件的清单。

这意味着从 java --module my.module 开始也不会检查清单。

我的看法

这很可悲,因为它破坏了 java 模块系统。如果您的依赖模块之一需要这些导出,您需要在命令行参数中设置它们,并且您需要提前知道。这通常是那些 sun.* 类 的情况,没有人应该使用但每个人都使用。 @Oracle:因此,如果我们无论如何都无法使用它,要么将其公开(因此无需添加导出),要么将其从 JRE 中完全删除...

解决方法

使用 URLClassLoader 在运行时自己动态加载依赖模块并自己进行导出(丑陋)。

String exports = jarFile.getManifest().getMainAttributes().getValue("Add-Exports");
if( exports != null )
{
    for (String moduleAndPackage : exports.split(" "))
    {
        String[] s = moduleAndPackage.trim().split("/");
        if (s.length != 2) continue;
        jdk.internal.module.Modules.addExports(ModuleLayer.boot().findModule(s[0]).orElseThrow(), s[1]);
    }
}

猜猜看,为了做到这一点,您需要使用受限制的 java.base/jdk.internal.module 包...如果您的程序可以使用 -jar.

这样可以防止在命令行中手动添加所有导出(您可能不知道)。