3

I currently develop an open-source project where people may add their own .jar to extend the included features. However, I'm stuck with how to load the classes in the jar.

I have an abstract class which has an abstract method onEnable() and some getter that provides some objects to work with the application. The plugin needs the subclass my plugin-class BasePlugin. The jar should be added to /plugins and thus I want all *.jar files in the /plugins folder to be loaded when the application starts.

The problem I'm running to now is that, of all the approaches I found, I need to declare a classpath of the classes in the jar file, which I do not know. Neither do I know the name of the jar file. Thus, I need to scan the /plugins folder for any *.jar file and load the corresponding class inside the jar which implements BasePlugin and invoke the onEnable() method.

8
  • 1
    I used a configuration file which provided the "entry point" into the plugin, placed within a "known"/"common" location within the Jar. The class loader used to load the Jar could then find and load this file, which would provide the name of the class which is the "entry point" for it Commented Apr 2, 2019 at 1:27
  • @MadProgrammer so basically one can add the name of the jar e.g. MyPluginExtension to the configuration file and my class looks for the a class called like this and then trys to load and invoke it? Commented Apr 2, 2019 at 1:29
  • 1
    No, you would create a "plugin" configuration file, which would be stored within the Jar as an embedded resource. All the "plugins" would have a file named the same, in the same location, which could then be read by the class loader. Within this file, it would contain the name of the class that conforms to the "base" class/interface requirements which the class loader would then load Commented Apr 2, 2019 at 1:36
  • 1
    @Nordic88 You don't need to. You simply list all the Jars in, let say, the "plugins" directory, use one or more class loaders to load them and then find the configuration Jar Commented Apr 2, 2019 at 1:43
  • 1
    @Nordic88 The basic idea involves using a URLClassloader, for example and example, then you can use findResource to find the "named" configuration, for example (although findResources will return multiple matches) Commented Apr 2, 2019 at 3:16

4 Answers 4

3

The basic idea is too...

  1. Read all the files in a specific directory
  2. Convert the File reference to a URL for each result
  3. Use a URLClassLoader, seeded with the URL results to load each result
  4. Use URLClassLoader#findResources to find all the match resources with a specific name
  5. Iterate over the matching resources and load each one, which should give, at least, the "entry point" class name
  6. Load the class specified by the "entry point" property

For example...

public List<PluginClass> loadPlugins() throws MalformedURLException, IOException, ClassNotFoundException { File plugins[] = new File("./Plugins").listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.getName().endsWith(".jar"); } }); List<URL> plugInURLs = new ArrayList<>(plugins.length); for (File plugin : plugins) { plugInURLs.add(plugin.toURI().toURL()); } URLClassLoader loader = new URLClassLoader(plugInURLs.toArray(new URL[0])); Enumeration<URL> resources = loader.findResources("/META-INFO/Plugin.properties"); List<PluginClass> classes = new ArrayList<>(plugInURLs.size()); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); Properties properties = new Properties(); try (InputStream is = resource.openStream()) { properties.load(is); String className = properties.getProperty("enrty-point"); PluginClass pluginClass = loader.loadClass(className); classes.add(pluginClass); } } return classes } 

nb: I've not run this, but this is the "basic

Sign up to request clarification or add additional context in comments.

Comments

3

SpigotMC uses JAR files as plugins as well, inside the jar is a plugin.yaml file that stores extra information about the plugin including the classpath. You don't need to use a YAML file, instead you could use something like JSON or even a plain text file.

The YAML file is inside the jar and can be accessed by using some of the methods explained here. You can then get the classpath property and then load the jar using the methods explained here. Extra information can be stored about the plugin such as the name, version, and dependencies.

Comments

1

Java already has a class for this: ServiceLoader.

The ServiceLoader class was introduced with Java 6, but the “SPI jar” concept is actually as old as Java 1.3. The idea is that each jar contains a short text file that describes its implementations of a particular service provider interface.

For instance, if a .jar file contains two subclasses of BasePlugin named FooPlugin and BarPlugin, the .jar file would also contain the following entry:

META-INF/services/com.example.BasePlugin 

And that jar entry would be a text file, containing the following lines:

com.myplugins.FooPlugin com.myplugins.BarPlugin 

Your project would scan for the plugins by creating a ClassLoader that reads from the plugins directory:

Collection<URL> urlList = new ArrayList<>(); Path pluginsDir = Paths.get( System.getProperty("user.home"), "plugins"); try (DirectoryStream<Path> jars = Files.newDirectoryStream(pluginsDir, "*.jar")) { for (Path jar : jars) { urlList.add(jar.toUri().toURL()); } } URL[] urls = urlList.toArray(new URL[0]); ClassLoader pluginClassLoader = new URLClassLoader(urls, BasePlugin.class.getClassLoader()); ServiceLoader<BasePlugin> plugins = ServiceLoader.load(BasePlugin.class, pluginClassLoader); for (BasePlugin plugin : plugins) { plugin.onEnable(); // etc. } 

An additional advantage of using ServiceLoader is that your code will be capable of working with modules, a more complete form of code encapsulation introduced with Java 9 which offers increased security.

Comments

0

There is an example here it may be helpful. Also, you should take a look at OSGi.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.