I am writing a Spring Boot API application, which has to have a plugin architecture. Following is a typical journey:
- My App can execute a task (taking some input and returning an output)
- I have an interface called
TaskExecutor
with a few methods (for example:execute(Object... inputArgs)
) - Users of my app write a plugin to handle a specific task (User simply implements the
TaskExecutor.execute(Object... args)
method to suit the requirements of the task). The plugin is packaged as a.jar
- I have a screen in my app called Install new Plugin where user can upload the
.jar
file containing the plugin. For example, the user uploads a new plugin calledsend-email-plugin-1.0.jar
and defines a new Task Type called Send Email. - Once uploaded, the system loads the jar to its classpath.
- Whenever the user creates a new task with type Send Email and runs it, the implementation of
TaskExecutor
in thesend-email-plugin-1.0.jar
is called.
I want to meet the requirements below:
- The plugin should be loaded at runtime
- It should be unloaded / updated (when there is a new
.jar
file) on demand - I must be able to sandbox it and allow / restrict access to certain resources on the host machine.
- The host application does not need to access any
beans
from the plugin - we can treat the plugin as a no-spring piece (for lack of a better term)
I have gone through articles on OSGi, PF4j and others, but can't seem to wrap my head around them - they look complicated for my use case (I'm not sure)
What I have tried so far:
As a very basic piece of code, I tried doing something like this (assuming I have the user's uploaded jar file stored somewhere in the file system).
@SuppressWarnings({ "rawtypes", "resource" })
// pluginName: name of the plugin (or the JAR file)
// entryPointClass: full name of the implementation class of TaskExecutor within this plugin. For ex: com.myorg.plugins.EmailTaskExecutorImpl
public synchronized TaskExecutor loadAdapter(String pluginName, String entryPointClass) {
try {
URL jarUrl = getJarAsURL(pluginName);
ClassLoader systemClassLoader = TaskExecutor.class.getClassLoader();
ClassLoader customLoader = new URLClassLoader(new URL[] {jarUrl}, systemClassLoader);
Class pluginEntryPoint = customLoader.loadClass(entryPointClass);
Object pluginEntryPointObject = pluginEntryPoint.newInstance();
return (TaskExecutor) pluginEntryPointObject;
} catch (MalformedURLException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
log.error("Error loading JAR for {}", pluginName, e);
return null;
}
}
It is able to do load the plugin at runtime and execute the Job. But, obviously it doesn't do the following:
- Does not handle updates (what if I want to upgrade the plugin to version 2.0?)
- It is not sandboxed. Has full access to server. How can I prevent that?
- Any thread safety / vulnerabilities that I have to take into account?
Request someone to point me in the right direction.
Thanks, Sriram