16

Java's ServiceLoader class is now officially baked into the Java language. Instead of looking for providers in META-INF/services you can now use the

provides <spiClass> with <providerClass>

What I fail to understand is, the use of uses in the service loading module declaration:

uses <spiClass>

Quoting from 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;
}

Why is it fundamental for the module system to know uses of a particular service, especially how will this introduce efficiency? Aren't services loaded lazily? Why can't the service loader just look for providers on the fly?

4

3 回答 3

8

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

为了使java.sql模块能够使用此驱动程序 [...],模块系统必须将驱动程序模块添加到模块图中并解决其依赖关系 [...]。

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

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

如果应用程序是一个模块,那么它的模块声明必须有一个uses指定服务的指令;这有助于找到提供者并确保它们能够可靠地执行

如果使用以下标志启动基于服务的应用程序,您可以观察到此行为--show-module-resolution

root monitor
monitor requires monitor.observer
[...]
monitor binds monitor.observer.beta
monitor binds monitor.observer.alpha

模块监视器 绑定模块monitor.observer.alphamonitor.observer.beta即使它不依赖于它们中的任何一个。

(引用来自模块系统的状态;强调我的。)

于 2017-08-22T09:00:49.287 回答
6

为什么模块系统知道特定服务的使用是基础...

因为依赖解析。 模块系统的状态在示例中在引用文本上方的几行中表示:

为了使java.sql模块能够使用该驱动程序,ServiceLoader该类必须能够通过反射实例化该驱动程序类;为此,模块系统必须将驱动模块添加到模块图中并解决其依赖关系......

关键是反射用于进行实例化。它发生模块解析之后......以及应用程序开始运行之后。

...尤其是这将如何提高效率?

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

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

于 2017-08-20T03:03:07.087 回答
3

引用 Java 9 javadoc 的ServiceLoader(强调由我添加):

load应用程序通过调用ServiceLoader的静态方法之一来获取给定服务的服务加载器。如果应用程序是一个模块,那么它的模块声明必须有一个指定服务的使用指令;这有助于找到提供者并确保它们能够可靠地执行。此外,如果服务不在应用程序模块中,则模块声明必须有一个requires指令,指定导出服务的模块。

于 2017-08-20T03:01:45.337 回答