DEV Community

Cover image for PlugFace Reborn - the Java Plugin System
Matteo Joliveau
Matteo Joliveau

Posted on • Updated on

PlugFace Reborn - the Java Plugin System

Visit https://plugface.matteojoliveau.com for the full documentation

One year ago I came across the need to dynamically load bits of code into my Java applications without having to recompile or repackage my software every time. Basically, I was looking for a plugin system.

There was only one open source plugin system worth noting at the time, PF4J, but I felt it was a bit clunky and cumbersome. I wanted something dead simple, that I could use to be productive in minutes. So I decided to write my own.

Enter PlugFace.
And the first iteration was sh*tty as hell, because at the time I was just an intern at my company, still learning real-world Java and didn't have the knowledge to write a good, usable framework.

The Ugly

The first implementation was not so simple as I wanted it to be.
There were interfaces to implement, reflection to use, configuration files to write, a sandboxed permission system, a half-working/half-broken attempt at dependency injection between plugins, and a lot of sweat and tears from me to both write it and use it.
Not the greatest software in the world.
Here is an example of an old PlugFace plugin:

package org.plugface.demo.plugins.greet;

import org.plugface.Plugin;
import org.plugface.PluginConfiguration;
import org.plugface.PluginStatus;
import org.plugface.demo.app.sdk.Greeter;
import org.plugface.impl.DefaultPluginConfiguration;

import java.util.Collections;

public class GreeterPlugin implements Plugin<String[], String>, Greeter{

    private final String name;
    private PluginConfiguration configuration;
    private PluginStatus status;
    private boolean enabled;

    public GreeterPlugin () {
        name = "greeter";
        configuration = new DefaultPluginConfiguration();
        status = PluginStatus.READY;
        enabled = false;
    }

    @Override
    public void start() {
        throw new UnsupportedOperationException("This plugin operates in single mode only");
    }

    @Override
    public void stop() {
        throw new UnsupportedOperationException("This plugin operates in single mode only");
    }

    @Override
    public String execute(String[] parameters) {
        return greet();
    }

    @Override
    public PluginConfiguration getPluginConfiguration() {
        return (PluginConfiguration) Collections.unmodifiableMap(configuration);
    }

    @Override
    public void setPluginConfiguration(PluginConfiguration configuration) {
        this.configuration = configuration;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public PluginStatus getStatus() {
        return status;
    }

    @Override
    public void setStatus(PluginStatus pluginStatus) {
        status = pluginStatus;
    }

    @Override
    public void enable() {
        this.enabled = true;
    }

    @Override
    public void disable() {
        this.enabled = false;
    }

    @Override
    public boolean isEnabled() {
        return enabled ;
    }

    @Override
    public String greet() {
        return "Hello PlugFace!":
    }

}
Enter fullscreen mode Exit fullscreen mode

These things were monsters, the interface was huge and full of debatably useful features (like status and name) and they were not practical to implement.
If you wanted more complex behaviors than 'start', 'stop' and 'execute', you had to add your own interfaces and cast your plugin to that type in order to use it or access the methods via reflection.

Then an idea stroke me. Why bother restricting the way a user interacts with a plugin when all the framework could deal with was the dynamic class loading?

The Good

With version 0.6, the framework revived. It was an (almost) complete rewrite. I ditched the whole interface story for a much cleaner @Plugin("name") annotation and ZERO opinions on how your plugin shall behave. Simply make it implement some interface your application is aware of and then access it through it.

Here is the same plugin but with the new system:

package org.plugface.demo.plugins.greet;

import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Greeter;

@Plugin("greeter")
public class GreeterPlugin implements Greeter {

    @Override
    public String greet() {
        return "Hello PlugFace!";
    }
}
Enter fullscreen mode Exit fullscreen mode

It implements the Greeter interface provided by my application. Now my code can simply retrieve the plugin an put it in a variable of type Greeter without ever have to be aware of the GreeterPlugin class. Neat!

Here is my application code.

final PluginManager manager = PluginManagers.defaultPluginManager();

manager.loadPlugins(PluginSources.jarSource("path/to/my/plugin/jars"));

final Greeter greeter = manager.getPlugin(Greeter.class);

greeter.greet(); // => "Hello PlugFace!"
Enter fullscreen mode Exit fullscreen mode

The PluginManager is a utility class to load plugins from various sources, another improvement over the old version which only featured hard-coded JAR loading, and accesses the PluginContext to add/get/remove plugins both by name and by type.

I also entirely ditched the Sandbox functionalities, which leveraged the Java SecurityContext features to restrict plugins in what they were allowed to do. As it was a huge pain to deal with, I preferred to remove it entirely to focus on the core features for this new version. I might reintroduce it in a future version if I find a way to implement it in a convenient fashion.

Dependency Injection done right

A feature the old system tried to introduce was the possibility of having plugins injected into each other. It achieved this by putting plugins into the PluginConfiguration object (which was basically a glorified Map<String, Object>, it worked but was fairly unpractical to use.

PlugFace 0.6 introduced a full-fledged dependency injection system via constructors, using the standard @javax.inject.Inject annotation (which will be made optional in the future in case of a single constructor being present) so that plugin classes can be used with any standard DI framework such as Spring or Guice.
It supports complicated dependency graphs using topological sorting, with circular dependencies detection and all that good stuff.

package org.plugface.demo.plugins.math;

import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Mathematics;

import javax.inject.Inject;

@Plugin("math")
public class MathPlugin implements Mathematics {

    private final SumPlugin sum;
    private final MultPlugin mult;

    @Inject
    public MathPlugin(SumPlugin sum, MultPlugin mult) {
        this.sum = sum;
        this.mult = mult;
    }

    @Override
    public Integer sum(int a, int b) {
        return sum.sum(a, b);
    }

    @Override
    public Integer mult(int a, int b) {
        return mult.mult(a, b);
    }
}
Enter fullscreen mode Exit fullscreen mode

Spring Integration

The Spring Framework is a tremendous toolkit in Java. It features pretty much anything, but its core feature is clearly the dependency injection system. Since I use Spring a lot in nearly all my projects, I wanted PlugFace to be well integrated with it.
If you want to use plain vanilla PlugFace you can import the plugface-core module, but if using Spring you may want to switch to the special plugface-spring module.
Not only it sports auto-configured beans for both the PluginManager and the PluginContext, but it also adds support for plugin retrieval from the ApplicationContext (meaning if an object is not found as a plugin, it will be looked up amongst Spring beans) and, perhaps more importantly, it makes Spring beans viable targets for dependency injection in plugins.

That's right, with plugface-spring you can not only inject other plugins inside your plugins, but also any Spring bean you have registered in your application.

Your plugins can access service classes, repositories, utility singletons, basically anything lives inside the Spring context.

package org.plugface.demo.plugins.user;

import org.plugface.core.annotations.Plugin;
import org.plugface.demo.app.sdk.Greeter;
import org.plugface.demo.app.sdk.TestService;

import javax.inject.Inject;

@Plugin("greeter")
public class UserPlugin implements UserDetails {

    private final UserService userService; //this is a Spring service

    @Inject
    public GreeterPlugin(UserService userService) {
        this.userService = userService;
    }

    @Override
    public String getUsername() {
        final User user = userService.getUserById(0L);
        return user.getUsername();
    }
}
Enter fullscreen mode Exit fullscreen mode

From Now On

In the future, I want to integrate with other DI frameworks as well, such as Guice, and expand upon the current feature set if any new use case for plugins in Java comes up.
Until then, thanks for the attention, and happy coding!

Discussion (1)

Collapse
nezam05 profile image
Nezam uddin

Is this project abandoned?