Java 9 模块系统是否支持可选依赖项?

Does the Java 9 Module system support optional dependencies?

背景

在 maven 中,工件可以声明依赖关系

<optional>true</optional>

这意味着不需要依赖项,但如果存在则可以使用。

The State of the Module System 似乎指定一个模块只能读取它需要的模块。

问题

用例

我有一个框架,它集成了应用程序可能会或可能不会使用的各种库。目前,这个框架是一个单一的 JAR,它反映了类路径以跳过缺少库的集成代码。

我想我们可以将其拆分为每个配置的单独模块,但这会导致 JAR 数量的组合爆炸,因为我们不仅需要为每个可选依赖项创建一个单独的 JAR,而且还需要一个大多数可选依赖项对的单独 JAR ...

是的,optional dependencies are supported. Quoting from the original proposal:

Extend the language of module declarations to allow the static modifier to be used on a requires directive, with the following meanings:

  • At compile time, requires static M expresses a mandatory dependence. It is an error if a suitable module cannot be found amongst the observable modules and resolved.

  • In phases after compile time, requires static M expresses an optional dependence. The module system will not search the observable modules for a suitable module during resolution, but if the resulting module graph contains a suitable module then it will add the appropriate readability edge prior to doing the usual post-resolution sanity checks. [...]

Thus a hypothetical module declaration of the form

module joda.beans {
    requires static joda.collect;
    ...
}

would ensure that the joda.collect module is available at compile time, so that code in the joda.beans module that refers to joda.collect can be compiled without any fuss. It would not, however, guarantee that joda.collect is available at link time or run time.

(同时,official documentation was created for that feature。)

我为此写了a demo。有趣的花絮是声明可选依赖项的模块的 module-info.java...

module org.codefx.demo.advent {
    // list the required modules
    requires org.codefx.demo.advent.calendar;
    // with 'static' the factories are only required at compile time;
    // to be present at run time either other modules most require them
    // or they must be added with the '--add-modules' command line option
    requires static org.codefx.demo.advent.factory.chocolate;
    requires static org.codefx.demo.advent.factory.quote;
}

... 以及同一模块中想要从其可选依赖项访问类型的代码。如果 ChocolateFactory and/or QuoteFactory 类型不存在,它必须写成优雅地失败:

private static List<SurpriseFactory> createSurpriseFactories() {
    return Stream.of(
            createChocolateFactoryIfAccessible(),
            createQuoteFactoryIfAccessible())
            .flatMap(Optional::stream)
            .collect(toList());
}

private static Optional<SurpriseFactory> createChocolateFactoryIfAccessible() {
    try {
        return Optional.of(new ChocolateFactory());
    } catch (NoClassDefFoundError er) {
        return Optional.empty();
    }
}

private static Optional<SurpriseFactory> createQuoteFactoryIfAccessible() {
    try {
        return Optional.of(new QuoteFactory());
    } catch (NoClassDefFoundError er) {
        return Optional.empty();
    }
}

最后,命令行可用于定义应用启动的模块:

$java \
    --add-modules org.codefx.demo.advent.factory.chocolate,org.codefx.demo.advent.factory.quote \
    -p mods -m org.codefx.demo.advent

当然也有可能其他模块非可选地需要它们,这会强制 JVM 将它们包含到模块图中。