I am building a client/server application. The client runs a small loader that downloads the client in the form of a module jar, but only if the client.jar has changed. The loader then attempts to run the client through ServiceLoader.
Here is the code that is to run the service provider in the client jar.
static PokerGameInstance getPokerGame() {
URL[] urls = null;
try {
urls = new URL[] { Paths.get("client.jar").toUri().toURL() };
System.out.println(urls[0]);
}
catch (Exception e) {
System.out.println("Could not create URL[] to use to create " +
"ClassLoader for client.jar.jar.");
return null;
}
URLClassLoader classLoader;
try {
classLoader = new URLClassLoader(urls);
}
catch (Exception e) {
System.out.println("Could not create classloader for " +
"client.jar.");
return null;
}
try { // Test code
classLoader.loadClass("com.brandli.jbpoker.client.PokerGame");
}
catch (ClassNotFoundException e) {
System.out.println("Could not find PokerGame class");
}
ServiceLoader<PokerGameInstance> loader = ServiceLoader
.load(PokerGameInstance.class, classLoader);
Optional<PokerGameInstance> optional = loader.findFirst();
if (optional.isEmpty()) {
System.out.println("Could not load client service provider.");
return null;
}
return optional.get();
}
The first time it runs, there is no client.jar. Other code downloads client.jar, and then the code above is run. Reviewing the output of this method, the URLClassLoader is able to load the service provider class (which happens to be called PokerTable). However, the ServiceLoader finds nothing, and the method prints "Could not load client service provider."
However, the second time it runs, client.jar is already there, and a fresh one is not downloaded. In that case, ServiceLoader returns the proper class and everything works.
I am running with a module path that includes the entire directory of jars. Client.jar is loaded there as well. So, in the second run, the system ClassLoader is picking up client.jar. In other words, the second pass works not because ServiceLoader is getting client.jar from URLClassLoader. I verified this by doing the second run with the ClassLoader parameter to ServiceLoader.load() set to null.
I also changed the module path to include only the discrete jars so that the system ClassLoader will not pick up client.jar if it is there. In that case, the code above always fails.
The upshot is that ServiceLoader is not recognizing the service in client.jar even though URLClassLoader will load the object. This has nothing to do with client.jar being downloaded, because the problem exists even if client.jar is there from the beginning (unless picked up by the system ClassLoader).
Remember that client.jar is a module jar. The code above is in a module that has this module-info.java:
module com.brandli.jbpoker.loader {
exports com.brandli.jbpoker.loader;
requires transitive javafx.controls;
requires transitive com.brandli.jbpoker.core;
uses com.brandli.jbpoker.loader.PokerGameInstance;
}
Client.jar has this module-info.java:
module com.brandli.jbpoker.client {
requires transitive javafx.controls;
requires transitive com.brandli.jbpoker.core;
requires transitive com.brandli.jbpoker.loader;
requires transitive com.brandli.jbpoker.common;
provides com.brandli.jbpoker.loader.PokerGameInstance with
com.brandli.jbpoker.client.PokerGame;
}
I suspect that this has something to do with modules. Anyone has any ideas?
java --help
also says it’s a list of directories. If you specified something else and it happened to work, I still would not consider that behavior I could rely on. – VGRModuleFinder#of(Path...)
accepts "directory of modules", "exploded modules", and "packaged modules". I would be surprised if the implementation ofjava --module-path
didn't use that implementation ofModuleFinder
, but there's no documentation saying one way or the other. – Slaw