6

I like services. I also like the module system. Unfortunately for me, before I used Java 9 I got in the habit of getting service providers from jars loaded at runtime via URLClassLoader, something like this (I'll use Java 10's var for brevity):

var url = new File("myjar.jar").toURI().toURL();
var cl = new URLClassLoader(new URL[] {url}, getClass().getClassLoader());
var services = ServiceLoader.load(MyService.class, cl);
for (var service : services) {
  ...
}

This works fine, even in Java 9 and beyond, but it loads the jar on the classpath, which means that it uses the old META-INF\services method to find service providers. I would rather use the module-info method, but that requires the jar to be loaded on the module path instead, but I couldn't find any way I might go about doing that. So here I am, hoping someone here who understands the module system more thoroughly will tell me how this can be done (or that it can't, if such is the case).

Naman
  • 27,789
  • 26
  • 218
  • 353
Anonymous
  • 491
  • 2
  • 12
  • 2
    Possible duplicate of [Is it possible to load and unload jdk and custom modules dynamically in Java 9?](https://stackoverflow.com/q/46112809/5221149) – Andreas Jan 17 '19 at 02:52
  • 1
    @Anonymous Well, could you update your question with the (module) structure you're following in terms of which module has the service, which one is using, what are the declaration used and the updated code trying to access the services. – Naman Jan 19 '19 at 03:22

2 Answers2

5

The smallest working example, I could assemble, is

var path = Path.of("myjar.jar");
var cl = new URLClassLoader(new URL[]{path.toUri().toURL()});
var mf = ModuleFinder.of(path);
var cfg = Configuration.resolve(mf, List.of(ModuleLayer.boot().configuration()), mf, Set.of());
var ml = ModuleLayer.defineModulesWithOneLoader(cfg, List.of(ModuleLayer.boot()), cl).layer();
var services = ServiceLoader.load(ml, MyService.class);
services.forEach(System.out::println);

Assuming myjar.jar is a modular jar declaring to provide MyService implementations.

Naman
  • 27,789
  • 26
  • 218
  • 353
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    I was about to make the edit along with the terms closer to the spec like `uses` and `provides`, but then the last line where you've assumed `myjar.jar` to be modular (with module-info.java) might not be true. Seems like OP is trying to use that as an automatic module instead. – Naman Jan 17 '19 at 16:47
  • This doesn't work for me. It compiles fine, but the `ServiceLoader` instance doesn't have any values in it. What should the `module-info.java` files (both provider and consumer) look like? Also @nullpointer, all the jars are modular, no auto modules involved. – Anonymous Jan 17 '19 at 22:14
  • 1
    @Anonymous if both of them are modular, then the producer `provides` the typename while the consumer `uses` it. for example, your current module shall have a declaration such that `uses some.sample.package.MyService` – Naman Jan 18 '19 at 00:36
  • @nullpointer I have `uses` and `provides` directives in the right places, yet still the `ServiceLoader` is empty. – Anonymous Jan 19 '19 at 03:20
0

Turns out I was using the wrong command line options (I didn't realize I wasn't supposed to use java -jar with modular jars). Using the right commands, @Holger's answer worked, except that the set passed to Configuration.resolve needed to contain all the names of the modules to be loaded, which was easy enough to fix:

var path = Path.of("myjar.jar");
var cl = new URLClassLoader(new URL[]{path.toUri().toURL()});
var mf = ModuleFinder.of(path);
var cfg = Configuration.resolve(mf, List.of(ModuleLayer.boot().configuration()), mf, mf.findAll().stream().map(module -> module.descriptor().name()).collect(Collectors.toSet()));
var ml = ModuleLayer.defineModulesWithOneLoader(cfg, List.of(ModuleLayer.boot()), cl).layer();
var services = ServiceLoader.load(ml, MyService.class);
services.forEach(System.out::println);
Anonymous
  • 491
  • 2
  • 12
  • You should be using `resolveAndBind` rather than `resolve`. If you move to that then you should find that you don't need to specify the root modules. Another point is that you do not need the URLClassLoader `cl` either. You can instead specify the system class loader as the parent. – Alan Bateman Jan 27 '19 at 16:25