Please let me suggest a slightly different approach for your problem. I hope it is working for you.
Spoiler: While trying to reproduce your problem, I found something that I think is missing in PluginClassLoader for handling dynamically loaded jars: It adds all URLs of the jars shipped as dependency with the plugin, but not the URL of the jar itself. This can be fixed. Let me show you how:
First my code to reproduce your problem. You didn't share your actual code, but I used the description from here to start: https://alef1.org/jean/jpf/
I created a simple plugin, which does actually nothing, just log messages when started and stopped.
public class SamplePlugin extends Plugin {
@Override
protected void doStart() {
System.out.println("Plugin started");
}
@Override
protected void doStop() {
System.out.println("Plugin stopped");
}
}
<?xml version="1.0" ?>
<!DOCTYPE plugin PUBLIC "-//JPF//Java Plug-in Manifest 1.0" "http://jpf.sourceforge.net/plugin_1_0.dtd">
<plugin id="org.example.string" version="0.0.4"
class="org.example.SamplePlugin">
</plugin>
And a Main class, which loads that plugin from a jar file in my case located under maven-pg-plugin/target:
public class Main {
public static void main(String[] args) throws IOException, JpfException {
ObjectFactory objectFactory = ObjectFactory.newInstance();
PluginManager pluginManager = objectFactory.createManager();
File pluginDir = new File("./maven-pg-plugin/target");
File[] plugins = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (plugins != null) {
PluginManager.PluginLocation[] locations = Arrays.stream(plugins).map(Main::createPluginLocation)
.toArray(PluginManager.PluginLocation[]::new);
Map<String, Identity> result = pluginManager.publishPlugins(locations);
for (Map.Entry<String, Identity> entry : result.entrySet()) {
Identity identity = entry.getValue();
String pluginId = identity.getId();
pluginManager.activatePlugin(pluginId);
pluginManager.deactivatePlugin(pluginId);
}
}
}
private static PluginManager.PluginLocation createPluginLocation(File file) {
try {
return StandardPluginLocation.create(file);
} catch (MalformedURLException e) {
// replace RuntimeException with something more suitable
throw new RuntimeException(e);
}
}
}
When running, the output of the sample plugin should be seen on the console.
That code works when the jar is also on the classpath, but stops working when removed from the classpath. The behaviour is the same with Java 8 as well as with newer versions.
Since PluginClassLoader is also an instance of URLClassLoader, i could fix it by calling addURL (which is protected) via reflection and add the jar of the plugin itself:
private static void fixPluginClassLoader(PluginManager pluginManager, String pluginId) {
PluginDescriptor descriptor = pluginManager.getRegistry().getPluginDescriptor(pluginId);
// retrieve the URL to the plugin jar through JPF
URL url = pluginManager.getPathResolver().resolvePath(descriptor, "");
PluginClassLoader pluginClassLoader = pluginManager.getPluginClassLoader(descriptor);
try {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(pluginClassLoader, url);
} catch (ReflectiveOperationException e) {
// replace RuntimeException with something more suitable
throw new RuntimeException(e);
}
}
And insert the call to that method before using the plugin:
for (Map.Entry<String, Identity> entry : result.entrySet()) {
Identity identity = entry.getValue();
String pluginId = identity.getId();
fixPluginClassLoader(pluginManager, pluginId); // <-- addition here
pluginManager.activatePlugin(identity.getId());
pluginManager.deactivatePlugin(identity.getId());
}
Tadaa! 🎉 For Java 11 this fix works out of the box, the jar doesn't need to be on the classpath and no extra command line arguments.
However with newer Java versions with extended module checks the command line argument --add-opens needs to be added to access the protected method of URLClassLoader:
--add-opens java.base/java.net=ALL-UNNAMED
(using ALL-UNNAMED assuming you are not actively creating Java modules)
Two notes of warning:
jpf-1.5.jar, I see eight of them on jpf-boot-1.5.jar. If you need it for production code, please consider migrating to something better maintained, e.g. an OSGi implementation.