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.

Reply via email to