Java 9 中 'uses' 指令的目的是什么?

What's the purpose of 'uses' directive in Java 9?

Java 的 ServiceLoader class 现已正式融入 Java 语言。您现在可以使用

而不是在 META-INF/services 中寻找提供商
provides <spiClass> with <providerClass>

我看不懂的是,服务加载模块声明中uses的使用:

uses <spiClass>

引用自The State of the Module System

The module system could identify uses of services by scanning the class files in module artifacts for invocations of the ServiceLoader::load methods, but that would be both slow and unreliable. That a module uses a particular service is a fundamental aspect of that module’s definition, so for both efficiency and clarity we express that in the module’s declaration with a uses clause:

module java.sql {
   requires transitive java.logging;
   requires transitive java.xml;
   exports java.sql;
   exports javax.sql;
   exports javax.transaction.xa;
   uses java.sql.Driver;
}

为什么模块系统知道特定服务的用途是基础,特别是这将如何提高效率?服务不是延迟加载的吗?为什么服务加载器不能即时寻找提供者?

引自Java 9 javadoc of ServiceLoader(重点是我加的):

An application obtains a service loader for a given service by invoking one of the static load methods of ServiceLoader. If the application is a module, then its module declaration must have a uses directive that specifies the service; this helps to locate providers and ensure they will execute reliably. In addition, if the service is not in the application module, then the module declaration must have a requires directive that specifies the module which exports the service.

Why is it fundamental for the module system to know uses of a particular service ...

因为依赖解析。 The State of the Module System 在您引用的文本上方的示例中说:

In order for the java.sql module to make use of this driver, the ServiceLoader class must be able to instantiate the driver class via reflection; for that to happen, the module system must add the driver module to the module graph and resolve its dependencies ...

关键是反射是用来做实例化的。它发生在 模块解析之后...并且在应用程序启动 运行.

之后

... especially how will this introduce efficiency?

扫描代码库以查找对 ServiceLoader::load 的所有调用非常昂贵。仅仅知道调用了一个方法是不够的(这可以通过分析 class 文件依赖性来完成)。您还需要知道使用了哪些参数 来确定将加载哪些classes。并且(正如 SotMS 文档所指出的那样)容易出错;例如如果参数是运行时表达式而不是编译时常量表达式。

他们采用的解决方案是提供一种方法来显式声明对反射加载的依赖性 class。

当 JVM 启动时,模块系统 resolves dependencies 并构建模块图。只有进入图表的模块在 运行 时间可用(即使其他模块是可观察的)。如果模块通过服务正确解耦,那么提供模块很有可能 而不是 初始模块的传递依赖。因此,如果不进一步努力,服务提供者模块通常不会进入模块图中,因此在模块尝试使用服务时 运行 时间不可用。

In order for the java.sql module to make use of this driver [...] the module system must add the driver module to the module graph and resolve its dependencies [...].

因此,为了使服务正常工作,提供者模块必须将其纳入模块图中,即使它们不是从初始模块传递来的。但是模块系统如何识别需要哪些模块作为服务提供者呢?所有这些都使用 provides 子句吗?那会有点过分。不,只应解决实际需要的服务提供者。

这使得有必要识别服务用途。正如其他人指出的那样,字节码分析速度慢且不可靠,因此需要一种更明确的机制来保证效率和正确性:uses 子句。只有有了它们,模块系统才能可靠高效地使所有服务提供者模块可用。

If the application is a module, then its module declaration must have a uses directive that specifies the service; this helps to locate providers and ensure they will execute reliably.

如果使用标志 --show-module-resolution:

启动 a service-based application,您可以观察到此行为
root monitor
monitor requires monitor.observer
[...]
monitor binds monitor.observer.beta
monitor binds monitor.observer.alpha

模块 monitor 绑定 模块 monitor.observer.alphamonitor.observer.beta 即使它不依赖于任何一个其中

(引自 The State of the Module System;强调我的。)