Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a service from module in a new created layer in Java 9?

I have three modules: module-a, module-b, module-c. Module-a and module-b are in boot layer. Layer for module-c I create myself.

Module-a has one interface com.mod-a.Service and in its module-info I have:

module module-a {
    exports com.mod-a;
}

Module-c implements com.mod-a.Service and in its module-info I have:

module module-c {
    requires module-a;
    provides com.mod-a.Service with com.mod-c.ServiceImpl;
}

Module-b creates new layer with module-c, and calls module-c service. In its module-info I have:

module module-b {
    requires module-a;
    requires java.management;
    requires slf4j.api;
    uses com.mod-a.Service;
}

In module-b I create new layer with module-c this way:

ModuleFinder finder = ModuleFinder.of(moduleCPath);
ModuleLayer parent = ModuleLayer.boot();
Configuration cf = parent.configuration().resolve(finder, ModuleFinder.of(), Set.of("module-c"));
ClassLoader scl = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl);
//the following line prints "module-c"
layer.modules().stream().map(Module::getName).forEach(System.out::println);

However, after creating layer I can't in module-b call Service of module-c. The following code:

Iterable<Service> it = ServiceLoader.load(Service.class);
System.out.println("LINE 1");
for (Service service : it) {
     System.out.println("Service was called");
     service.doIt();
}
System.out.println("LINE 2");

outputs:

LINE 1
LINE 2

What is my mistake?

like image 228
Pavel_K Avatar asked Oct 17 '22 05:10

Pavel_K


2 Answers

ServiceLoader.load(Class) uses the TCCL as the starting point to locate service providers for the service whereas your example should use the child layer or alternative the class loader of any class loader defining modules in the layer. So if you change the example to ServiceLoader.load(layer, Service.class) then it should work as you expect.

Separately, you've used resolve and specified the service provider module as the root module to resolve. Nothing wrong with that but an alternative would have been to use resolveAndBind and not specify any root modules. The uses com.mod-a.Service in module-b will ensure that modules that provides com.mod-a.Service will be resolved.

like image 133
Alan Bateman Avatar answered Oct 20 '22 23:10

Alan Bateman


The root cause of your issue is that the

ServiceLoader.load(Service.class) 

which is an alternate of

ServiceLoader.load(Service.class, Thread.currentThread().getContextClassLoader())

doesn't end up finding any service provider for the Service.

One way in which I was able to fix that was to open the package of the service provider to the module which owns the service, as :

module module-c {
    requires module-a;
    provides com.mod-a.Service with com.mod-c.ServiceImpl;
    opens com.mod-c to module-a;
}

Also, would suggest going through the ServiceLoader on how to deploy service providers as module and on the classpath.

like image 23
Naman Avatar answered Oct 21 '22 01:10

Naman