ServiceLoader and ModuleLayer

Alan Bateman Alan.Bateman at oracle.com
Tue Sep 26 20:44:17 UTC 2017


On 26/09/2017 20:37, Sander Mak wrote:
> I'm currently running into an issue that behaves unexpected as far as I can see. Let's say there are two service types, A and B. The module `main` in the boot layer has a uses constraint on A. Module `main` instantiates a new ModuleLayer with the following code:
>
>        ModuleFinder finder = ModuleFinder.of(dir.toPath());
>
>        ModuleLayer parent = ModuleLayer.boot();
>        Configuration cf = parent.configuration()
>          .resolveAndBind(finder, ModuleFinder.of(), Set.of());
>
>        ClassLoader scl = ClassLoader.getSystemClassLoader();
>        ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl);
>
>        // Now use new A providers from the layer:
>        ServiceLoader.load(layer, A.class).forEach(...)
>
> When `dir` contains a single provider module that provides an implementation of A, this works fine.
Yes, you've provided the child layer as the starting point so it will 
load the A providers in that layer, then the A providers in the parent 
layer. An alternative would be to specify the class loader for any 
module in the layer (doesn't matter which one although there is only one 
in your example).

> What doesn't work, is if I have a directory with a provider module providing an A implementation, where this A implementation in turn has a uses constraint on B. When I check `layer.modules()`, I can see that the B provider modules do get resolved into the layer (they're also in `dir`). However, `ServiceLoader.load(B.class)`, which is part of the A service implementation code, returns no instances. How can I make sure the B service providers are bound as well within the layer?
The 1-arg load method uses the TCCL as the starting point and it's 
probably the application class loader in your case. In container 
environments where there is a TCCL per application then the 1-arg load 
method works well.

>
> To answer my own question, after some thinking and experimentation I found out that the A implementation should use the following code:
>
>       ServiceLoader.load(getClass().getModule().getLayer(), B.class)
>
> So... should I start writing all my ServiceLoader calls this way from now on, if I want to make sure my modules work regardless of whether they're loaded in the boot layer or another layer? Wouldn't it be more logical for ServiceLoader to always work from the current layer (hm, that would probably break the current classloader based contract)? Am I missing another option?
There's any notion of "current layer" and not clear that it would be 
useful. To explain why, assume that the A service type in your example 
is one of the service types that the java.xml module uses. Now suppose 
that some code in the child layer invokes an API in the java.xml module 
to parse a document. In that scenario you have code in the boot layer 
loading service providers in a child layer. If the XML code only used 
the "current layer" then it would never load providers in child layers.

However to your question, then specifying a starting point (be it layer 
or class loader) may be needed to allow libraries be used in complicated 
environments.

-Alan.


More information about the jigsaw-dev mailing list