ServiceLoader and ModuleLayer

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

ServiceLoader and ModuleLayer

Sander Mak
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. 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?

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?


Sander



Reply | Threaded
Open this post in threaded view
|

Re: ServiceLoader and ModuleLayer

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