0
votes

I am trying to load plugins from a certain jar file using the java ServiceLoader with an UrlClassLoader, but I just cannot seem to get it to find my plugin classes. Building both modules works, but whenever I run the code below, I get a java.util.NoSuchElementException, even though the file path is correct and I do not see where I messed up

I have to modules in my IntelliJ project, Test (The application loading the plugin and also providing the ServiceProvider) and PluginTest (The plugin to be loaded).

This is a screenshot of the structure of the Test module:

Test Module

And here is a screenshot of the structure of the PluginTest module:

PluginTest Module

Here is the ServiceProvider PluginProvider:

package net.lbflabs;

public abstract class PluginProvider {

    public abstract String getSecretMessage(int message);
}

This is the plugin that extends this class:

package net.lbflabs.plugins;

import net.lbflabs.PluginProvider;

public class PluginClass extends PluginProvider {
    @Override
    public String getSecretMessage(int message) {
        return "Here is my secret.";
    }
}

Here is the main class that loads the plugin:

package net.lbflabs;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ServiceLoader;

public class Main {

    private static String path = "C:/Users/jacke/IdeaProjects/Tests/out/artifacts/PluginTest_jar/PluginTest.jar";

    public static void main(String[] args) throws MalformedURLException {
        System.out.println("Initializing... \nPreparing to load JAR from Path " + path + "...");
        File file = new File(path);
        System.out.println(file.exists()); //Prints true, so file does exist
        URLClassLoader c = new URLClassLoader((new URL[]{file.getAbsoluteFile().toURI().toURL()}));
        ServiceLoader<PluginProvider> loader = ServiceLoader.load(PluginProvider.class, c);
        PluginProvider p = loader.iterator().next(); // Throws the java.util.NoSuchElementException
        System.out.println("Secret message in plugin: " + p.getSecretMessage(1));
    }
}

Here is the output when running the Tests module:

"C:\Program Files\Java\jdk-14.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3\lib\idea_rt.jar=50330:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.3\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\jacke\IdeaProjects\Tests\out\production\Tests net.lbflabs.Main
Initializing... 
Preparing to load JAR from Path C:/Users/jacke/IdeaProjects/Tests/out/artifacts/PluginTest_jar/PluginTest.jar...
true
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.ServiceLoader$2.next(ServiceLoader.java:1310)
    at java.base/java.util.ServiceLoader$2.next(ServiceLoader.java:1298)
    at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1396)
    at net.lbflabs.Main.main(Main.java:19)

Process finished with exit code 1

The service file in the PluginTest module I believe is correctly named (net.lbflabs.PluginProvider) and contains the valid class name (net.lbflabs.plugins.PluginClass). If it was incorrect, IntelliJ would probably find the issue since when I introduce a typo I get warnings.

If someone needs it, I can provide the whole project as .rar (please tell me how you want me to share it, e.g. I create a onedrive link)

PS: I have already asked this question here, but since I was working on a bigger project and hesitant to share all the code in it (and since the only answer I got did not work, probably due to not enough info from me), I wanted to repost the issue with a minimum workable example that throws the same exception, I hope that is fine.

1
The classloader doesn't contain the parent class only the implementing class, so it cannot find the parent class. So I suspect due to incomplete class-hierarchy it cannot load that class. So you should use the constructor which takes a parent class loader and set it to the current one. So that a proper class hierarchy can be determined.M. Deinum
Would it not throw a different exception then? I had my classloader thow exceptions like NoClassDefFoundError on similar occations. You might still be right tho. How would I fix the issue? Could you post your fix as answer so I can potentially accept it?Jakob Tinhofer
Wouldn't be the first that one exception is hidden by another and looking at the LazyClassPathLookupIterator (an internal class) it actually catches the ClassNotFoundException and simply returns null. Leading to the exception you get.M. Deinum

1 Answers

1
votes

One thing I notice is that you construct a classloader which only contains 1 class. With this it won't be able to create a proper class hierarchy and most likely fail. Instead pass the current Classloader as the parent to your URLClassLoader.

ClassLoade parent = PluginProvider.class.getClassLoader();
URL[] urls = new URL[] { file.getAbsoluteFile().toURI().toURL()};
URLClassLoader c = new URLClassLoader(urls, parent);

Now a proper hierarchy should be able to be constructed.

Another thing is in your project structure your META-INF directory isn't under the src directory containing the sources for the project. Which means Intellij will leave them out of the jar. So you are basically adding a jar without the proper files. It contains only the class and not META-INF/services/net.lbflabs.PluginProvider.