Getting started
It’s very simple to add PF4J in your application:
public static void main(String[] args) {
...
PluginManager pluginManager = new DefaultPluginManager();
pluginManager.loadPlugins();
pluginManager.startPlugins();
...
}
In above code, I created a DefaultPluginManager (it’s the default implementation for PluginManager interface) that loads and starts all active(resolved) plugins.
Each available plugin is loaded using a different java class loader, PluginClassLoader.
The PluginClassLoader contains only classes found in PluginClasspath (default classes and lib folders) of plugin and runtime classes and libraries of the required/dependent plugins. This class loader is a Parent Last ClassLoader - it loads the classes from the plugin’s jars before delegating to the parent class loader.
The plugins are stored in a folder. You can specify the plugins folder in the constructor of DefaultPluginManager. If the plugins folder is not specified
then the location is returned by System.getProperty("pf4j.pluginsDir", "plugins")
.
NOTE: The “pf4j.pluginsDir” property comes with comma-separated directory list support (support for multiple plugin root directories).
The structure of plugins folder is:
- plugin1.zip (or plugin1 folder)
- plugin2.zip (or plugin2 folder)
In plugins folder you can put a plugin as folder or archive file (zip). A plugin folder has this structure by default:
classes
folderlib
folder (optional - if the plugin used third party libraries)
The plugin manager searches plugins metadata using a PluginDescriptorFinder.
DefaultPluginDescriptorFinder is a “link” to ManifestPluginDescriptorFinder that lookups plugins descriptors in MANIFEST.MF file.
In this case the classes/META-INF/MANIFEST.MF
file looks like:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: decebal
Build-Jdk: 1.6.0_17
Plugin-Class: org.pf4j.demo.welcome.WelcomePlugin
Plugin-Dependencies: x, y, z
Plugin-Id: welcome-plugin
Plugin-Provider: Decebal Suiu
Plugin-Version: 0.0.1
In above manifest I described a plugin with id welcome-plugin
, with class org.pf4j.demo.welcome.WelcomePlugin
, with version 0.0.1
and with dependencies
to plugins x, y, z
.
NOTE: The plugin version must be compliant with Semantic Versioning (PF4J uses jsemver
as implementation for SemVer because it comes with support for comparing versions)
You can define an extension point in your application using ExtensionPoint interface marker.
public interface Greeting extends ExtensionPoint {
String getGreeting();
}
Another important internal component is ExtensionFinder that describes how the plugin manager discovers extensions for the extensions points.
DefaultExtensionFinder looks up extensions using Extension annotation.
DefaultExtensionFinder looks up extensions in all extensions index files META-INF/extensions.idx
. PF4J uses Java Annotation Processing to process at compile time all classes annotated with @Extension and to produce the extensions index file.
public class WelcomePlugin extends Plugin {
public WelcomePlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Extension
public static class WelcomeGreeting implements Greeting {
public String getGreeting() {
return "Welcome";
}
}
}
In above code I supply an extension for the Greeting
extension point.
You can retrieve all extensions for an extension point with:
List<Greeting> greetings = pluginManager.getExtensions(Greeting.class);
for (Greeting greeting : greetings) {
System.out.println(">>> " + greeting.getGreeting());
}
The output is:
>>> Welcome
>>> Hello
You can inject your custom component (for example PluginDescriptorFinder, ExtensionFinder, PluginClasspath, …) in DefaultPluginManager just override create...
methods (factory method pattern).
Example:
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new PropertiesPluginDescriptorFinder();
}
and in plugin repository you must have a plugin.properties file with the below content:
plugin.class=org.pf4j.demo.welcome.WelcomePlugin
plugin.dependencies=x, y, z
plugin.id=welcome-plugin
plugin.provider=Decebal Suiu
plugin.version=0.0.1
You can control extension instance creation overriding createExtensionFactory
method from DefaultExtensionFinder.
Also, you can control plugin instance creation overriding createPluginFactory
method from DefaultExtensionFinder.
For more information please see the demo sources.
NOTE: If your application didn’t find extensions then make sure that you have a file with name extensions.idx
generated by PF4J in the plugin jar.
It’s most likely that they are some problems with the annotation processing mechanism from Java.
One possible solution to resolve your problem is to add a configuration to your maven build.
The maven-compiler-plugin
can be configured to do this like so:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<annotationProcessors>
<annotationProcessor>org.pf4j.processor.ExtensionAnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>