Java Plugin Architecture 2016-10-16

In this post I will show how the plugin system in Chunky is being implemented.

Plugin support is surprisingly easy to add in a Java application thanks to the Java reflection API. Java reflection allows runtime type introspections, and dynamic code loading. Some of the more exotic uses cases for Java reflection is to generate executable code during runtime. A compiler hosted on and targeting the JVM could even use reflection for compile-time code execution. You don’t need to build anything nearly so exotic to implement a simple plugin system.

A minimal Java plugin system

Let’s build the simplest possible plugin system in Java! First, we define a plugin interface:

package plugin;
public interface Plugin {
  void attach(host.Application app);
}

The host application will call the attach() method when loading the plugin. The host application passes a reference to itself so that the plugin can register callbacks and thereby extend the behavior of the host application. Here is a minimal plugin that just prints a message to standard out when the host loads the plugin:

package myplugin;
public class MyPlugin implements plugin.Plugin {
  @Override public void attach(host.Application app) {
    System.out.println("Hello from MyPlugin!");
  }
}

The host application should not depend on the plugin implementation, only the Plugin interface. Loading a plugin requires knowing the class name of the plugin, so that can be supplied as a command-line argument. Here is an example of what the host application might look like:

package host;

public class Application {
  public static void main(String[] args)
      throws Exception {
    if (args.length < 1) {
      throw new Error("Argument 1 should be a plugin class name.");
    }
    new Application().loadPlugin(args[0]);
  }

  private void loadPlugin(String pluginClassName)
      throws Exception {
    Class<?> pluginClass = getClass().getClassLoader()
        .loadClass(pluginClassName);
    plugin.Plugin instance =
        (plugin.Plugin) pluginClass.newInstance();
    instance.attach(this);
  }
}

The host application uses the Java class loader that loaded the application itself to load the plugin class. This means that the plugin must be supplied in the Java classpath when launching the host application. Here is an example of how to run the host application with the plugin on Linux, if both have been compiled to separate Jar files:

$ java -cp host.jar:plugin.jar host.Application myplugin.MyPlugin

And here is what it looks like when run on Windows:

Running the plugin on Windows

I uploaded the code above to this GitHub repository, in case you want to play around with it yourself.

The Chunky plugin system

For Chunky I made a slightly more advanced plugin system. I wanted to avoid depending on the classpath being setup correctly for plugins to be loaded. Instead the path to plugin Jar files are stored in the Chunky configuration file along with some metadata including the plugin class name.

A new URLClassLoader is created for each plugin to load the plugin classes. This is necessary since the plugins are not on the Chunky classpath:

URLClassLoader classLoader = new URLClassLoader(new URL[] {pluginJar.toURI().toURL()});
Class<?> pluginClass = classLoader.loadClass(pluginClassName);
...

The metadata for each plugin includes an MD5 checksum to verify that the Jar file is intact. The MD5 checksum is only for basic integrity checking, not security. A plugin can do nearly anything it wants to with the computer running Chunky so it is very important that users only install plugins they trust.

It would be nice to also have some kind of version compatibility system for plugins. One way to do that is that the plugin could specify which version of Chunky it was built for, and then a warning is shown when loading a plugin that was built for an older version of Chunky.

So far I have only added a few basic factory interfaces which plugins can implement to modify certain parts of Chunky. More factory interfaces and callbacks will be added to the plugin API as the need arises. To figure out what should be in the API I need to have use cases that guide the development. So far I have made a handful of plugins myself, but I hope that other developers will start building their own plugins and help guide the plugin API development.

Demo plugins

Here is a screenshot showing a plugin which modifies the rendering code by injecting an ambient occlusion renderer into Chunky:

Ambient Occlusion Plugin

Here is a screenshot from another plugin I made which changes the rendering for grass blocks so that they render as lava:

Lavagrass

I also made a separate plugin manager application:

Chunky Plugin Manager

The plugin manager is hosted in this GitHub repository. I will integrate the plugin manager into the Chunky launcher for a later Chunky release when the plugin API has matured more.

Here is the current list of plugin demos that I have uploaded to GitHub:

Categories: Uncategorized

Leave a Reply

Your email address will not be published. Required fields are marked *