20

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?

Roland
  • 7,525
  • 13
  • 61
  • 124
Mordechai
  • 15,437
  • 2
  • 41
  • 82

3 Answers3

12

When the JVM launches, the module system resolves dependencies and builds the module graph. Only modules that make it into the graph are available at run time (even if others are observable). If modules are properly decoupled via services, there is a good chance, though, that the providing modules are not transitive dependencies of the initial module. So without further efforts, service provider modules would routinely not make it into the module graph and thus not be available at run time when a module tries to use a service.

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 [...].

So for services to properly work, provider modules must make it into the module graph even if they are not transitively required from the initial module. But how can the module system identify which modules are needed as service providers? All that use a provides clause? That would be a little too much. No, only providers of services that are actually needed should be resolved.

This makes it necessary to identify services uses. As others have pointed out, bytecode analysis is slow and unreliable, so a more explicit mechanism is needed to guarantee efficiency and correctness: uses clauses. Only with them can the module system reliably and efficiently make all service provider modules available.

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.

You can observe this behavior if launching a service-based application with the flag --show-module-resolution:

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

The module monitor binds the modules monitor.observer.alpha and monitor.observer.beta even though it does not depend on either of them.

(Quotes are from The State of the Module System; emphasis mine.)

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • Finally a clear answer. But as I already asked @StephenC; if so, how can one load classes with reflection where its name is not known until runtime, as classes passed as system property? Of course the module system doesn't scan for `Class::forName`. – Mordechai Aug 22 '17 at 13:30
  • Another question. Wouldn't a provider always require the service module? It has to implement an interface contained in that module, no? Wouldn't this suffice to add it in the graph? – Mordechai Aug 22 '17 at 13:41
  • Oops, missed your comments. Re first one on reflection: The module system **wants to guarantee** that service providers make it into the graph. It has no such desire for reflective access with `Class::forName`. If the module with that class is present, fine; otherwise, it's your bad luck - the module system simply doesn't care. – Nicolai Parlog Sep 10 '17 at 23:18
  • Re second comment on dependencies on the module defining the service type: Both the module providing and consuming the service need that module (_provider -> service_ and _consumer -> service_). When encountering _consumer_ the module system will add _service_ to the graph, but why would it add _consumer_? That would be the case for _service -> consumer_, which is never the case. Should it suddenly go the other direction during resolution because somebody required a service? – Nicolai Parlog Sep 10 '17 at 23:23
7

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

Because of dependency resolution. The State of the Module System says in the example a few lines above your quoted text:

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 ...

The key point is that reflection is used to do the instantiation. It happens after module resolution ... and after the application has started running.

... especially how will this introduce efficiency?

Scanning a codebase for all calls to ServiceLoader::load is expensive. It is not sufficient to just know that a method is called (which could be done by analysis of classfile dependencies). You also need to know what parameters were used to determine what classes are going to be loaded. And (as the SotMS document points out) that would be open to errors; e.g. if the parameters are runtime expressions rather than compile-time constant expressions.

The solution they have adopted is to provide a way to explicitly declare a dependency on a reflectively loaded class.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
6

Quoting from Java 9 javadoc of ServiceLoader (emphasis added by me):

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.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • How is loading providers more efficient if I know who uses it? Isn't it enough to declare that it's a provider with the `provides` directive and let the service loader look for it? – Mordechai Aug 20 '17 at 05:25
  • 3
    @MouseEvent Are you asking about the internal optimizations of module management? If so, you should look at the source code. Can't you just accept that specifying `uses` helps the system, and go with that? – Andreas Aug 20 '17 at 06:57
  • It says that without `uses` it has to scan uses of `ServiceLoader::load`. Why? – Mordechai Aug 20 '17 at 14:52