1 介绍
PF4J(Plugin Framework for Java)是一款轻量级Java插件框架,通过ExtensionPoint机制实现了松耦合的插件扩展,支持热插拔、隔离、插件的生命周期管理等能力
1.1 优点:
-
低侵入性
public interface DataProcessor extends ExtensionPoint { String process(String input); }
-
动态加载与热插拔
pluginManager.loadPlugin(pluginId); // 动态加载 pluginManager.startPlugin(pluginId); // 激活插件
-
依赖隔离
每个插件使用独立的ClassLoader,避免类冲突,尤其适合多团队协作开发。 -
灵活的生命周期管理
提供start()、stop()、delete()等钩子方法,方便管理插件资源。 -
支持Spring Framework集成
1.2 缺点:
- 泛型支持有限
// 警告:Raw use of parameterized class 'ExtensionPoint' List<ExtensionPoint> extensions = pluginManager.getExtensions(ExtensionPoint.class);
- 性能开销
频繁加载/卸载插件可能引发内存泄漏,需谨慎管理生命周期。
1.3 原理:
2. 完整示例尝试
2.1 工程结构
├── `spring-pf4j-api` #主程序和插件的接口契约
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
│ └── com.hushow.plugin.demo.service
│ └── IDemoExtensionPoint.java
├── `spring-pf4j-main` #主程序
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
│ └── com.hushow.plugin.demo
│ └── DefaultExtensionPoint.java
│ └── PluginMain.java
└── spring-pf4j-plugins
├── `plugin1` #插件1
│ ├── pom.xml
│ └── src
│ └── main
│ ├── java
│ │ └── com.hushow.plugin.demo
│ │ └── Plugin1.java #插件1生命周期钩子
│ │ └── Plugin1ExtensionPointImpl.java #插件1接口实现
│ └── resources
│ └── plugin.properties
└── `plugin2` #插件2
├── pom.xml
└── src
└── main
├── java
│ └── com.hushow.plugin.demo
│ └── Plugin2.java #插件1生命周期钩子
│ └── Plugin2ExtensionPointImpl.java #插件2接口实现
└── resources
└── plugin.properties
2.2 扩展点接口 (api模块)
/**
* 统一查询接口
*/
public interface IDemoExtensionPoint<R> extends ExtensionPoint {
/**
* 根据id查询
* @param id
* @return
*/
R getById(String id);
}
2.3 插件1实现 (plugin1模块)
# Plugin1ExtensionPointImpl.java
package com.hushow.plugin.demo;
import com.hushow.plugin.demo.service.IDemoExtensionPoint;
import org.pf4j.Extension;
/**
* 插件1扩展点实现
*/
@Extension
public class Plugin1ExtensionPointImpl implements IDemoExtensionPoint<Integer> {
@Override
public Integer getById(String id) {
return 1000;
}
}
2.4 插件2实现 (plugin2模块)
// Plugin2ExtensionPointImpl.java
package com.hushow.plugin.demo;
import com.hushow.plugin.demo.service.IDemoExtensionPoint;
import org.pf4j.Extension;
/**
* 插件2扩展点实现
*/
@Extension
public class Plugin2ExtensionPointImpl implements IDemoExtensionPoint<String> {
@Override
public String getById(String id) {
return "Plugin2ExtensionPointImpl getByid:"+id;
}
}
2.5 主应用程序
// PluginMain.java
package com.hushow.plugin.demo;
import com.hushow.plugin.demo.service.IDemoExtensionPoint;
import org.pf4j.DefaultPluginManager;
import org.pf4j.PluginManager;
import java.util.List;
public class PluginMain {
public static void main(String[] args) {
// 创建插件管理器
//PluginManager pluginManager = new DefaultPluginManager(Paths.get("/plugins"));
PluginManager pluginManager = new DefaultPluginManager();
// 加载插件
pluginManager.loadPlugins();
// 启动插件
pluginManager.startPlugins();
// 获取所有扩展实现
List<IDemoExtensionPoint> epList = pluginManager.getExtensions(IDemoExtensionPoint.class);
// 使用扩展
for (IDemoExtensionPoint<?> ep : epList) {
System.out.println(ep.getById("11"));
}
//// 停止并卸载插件
pluginManager.stopPlugins();
pluginManager.unloadPlugins();
}
}
2.6 构建插件
每个插件需要在 src/main/resources
下创建 plugin.properties
文件:
plugin1的plugin.properties:
plugin.id=plugin1
plugin.class=com.hushow.plugin.demo.Plugin1
plugin.version=0.0.1
plugin.provider=hushowly
plugin.dependencies=
plugin2的plugin.properties:
plugin.id=plugin2
plugin.class=com.hushow.plugin.demo.Plugin2
plugin.version=0.0.1
plugin.provider=hushowly
plugin.dependencies=
2.7 运行步骤
-
构建所有模块:
mvn clean package
-
创建plugins目录并复制插件jar:
mkdir -p spring-pf4j-main/plugins cp plugin1/target/plugin1-1.0.0.jar spring-pf4j-main/plugins/ cp plugin2/target/plugin2-1.0.0.jar spring-pf4j-main/plugins/
-
运行主应用程序:
cd spring-pf4j-main java -jar target/spring-pf4j-main.jar
2.8 预期输出
hushow@hushow-pc:~/wk-tmp/learning-demo/spring-plugin-demo/spring-pf4j-demo/spring-pf4j-main$ java -jar ./target/spring-pf4j-main-0.0.1-SNAPSHOT.jar
17:12:28.169 [main] INFO org.pf4j.DefaultPluginStatusProvider - Enabled plugins: []
17:12:28.178 [main] INFO org.pf4j.DefaultPluginStatusProvider - Disabled plugins: []
17:12:28.187 [main] INFO org.pf4j.DefaultPluginManager - PF4J version 3.6.0 in 'deployment' mode
17:12:28.187 [main] DEBUG org.pf4j.AbstractPluginManager - Lookup plugins in '[plugins]'
17:12:28.206 [main] DEBUG org.pf4j.AbstractPluginManager - Found 2 possible plugins: [plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar, plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar]
17:12:28.207 [main] DEBUG org.pf4j.AbstractPluginManager - Use 'org.pf4j.CompoundPluginDescriptorFinder@46ee7fe8' to find plugins descriptors
17:12:28.207 [main] DEBUG org.pf4j.AbstractPluginManager - Finding plugin descriptor for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.207 [main] DEBUG org.pf4j.CompoundPluginDescriptorFinder - 'org.pf4j.PropertiesPluginDescriptorFinder@7506e922' is applicable for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.241 [main] DEBUG org.pf4j.PropertiesPluginDescriptorFinder - Lookup plugin descriptor in 'plugin.properties'
17:12:28.248 [main] DEBUG org.pf4j.AbstractPluginManager - Found descriptor PluginDescriptor [pluginId=plugin1, pluginClass=com.hushow.plugin.demo.Plugin1, version=0.0.1, provider=hushowly, dependencies=[], description=, requires=*, license=null]
17:12:28.248 [main] DEBUG org.pf4j.AbstractPluginManager - Class 'com.hushow.plugin.demo.Plugin1' for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.248 [main] DEBUG org.pf4j.AbstractPluginManager - Loading plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.249 [main] DEBUG org.pf4j.CompoundPluginLoader - 'org.pf4j.JarPluginLoader@4f8e5cde' is applicable for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.251 [main] DEBUG org.pf4j.PluginClassLoader - Add 'file:/home/hushow/wk-tmp/learning-demo/spring-plugin-demo/spring-pf4j-demo/spring-pf4j-main/plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.252 [main] DEBUG org.pf4j.AbstractPluginManager - Loaded plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar' with class loader 'org.pf4j.PluginClassLoader@3b764bce'
17:12:28.252 [main] DEBUG org.pf4j.AbstractPluginManager - Creating wrapper for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.254 [main] DEBUG org.pf4j.AbstractPluginManager - Created wrapper 'PluginWrapper [descriptor=PluginDescriptor [pluginId=plugin1, pluginClass=com.hushow.plugin.demo.Plugin1, version=0.0.1, provider=hushowly, dependencies=[], description=, requires=*, license=null], pluginPath=plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar]' for plugin 'plugins/pf4j-demo-plugin1-0.0.1-SNAPSHOT.jar'
17:12:28.254 [main] DEBUG org.pf4j.AbstractPluginManager - Use 'org.pf4j.CompoundPluginDescriptorFinder@46ee7fe8' to find plugins descriptors
17:12:28.254 [main] DEBUG org.pf4j.AbstractPluginManager - Finding plugin descriptor for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.255 [main] DEBUG org.pf4j.CompoundPluginDescriptorFinder - 'org.pf4j.PropertiesPluginDescriptorFinder@7506e922' is applicable for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.256 [main] DEBUG org.pf4j.PropertiesPluginDescriptorFinder - Lookup plugin descriptor in 'plugin.properties'
17:12:28.257 [main] DEBUG org.pf4j.AbstractPluginManager - Found descriptor PluginDescriptor [pluginId=plugin2, pluginClass=com.hushow.plugin.demo.Plugin2, version=0.0.1, provider=hushowly, dependencies=[], description=, requires=*, license=null]
17:12:28.257 [main] DEBUG org.pf4j.AbstractPluginManager - Class 'com.hushow.plugin.demo.Plugin2' for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.257 [main] DEBUG org.pf4j.AbstractPluginManager - Loading plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.257 [main] DEBUG org.pf4j.CompoundPluginLoader - 'org.pf4j.JarPluginLoader@4f8e5cde' is applicable for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.257 [main] DEBUG org.pf4j.PluginClassLoader - Add 'file:/home/hushow/wk-tmp/learning-demo/spring-plugin-demo/spring-pf4j-demo/spring-pf4j-main/plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.258 [main] DEBUG org.pf4j.AbstractPluginManager - Loaded plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar' with class loader 'org.pf4j.PluginClassLoader@45fe3ee3'
17:12:28.258 [main] DEBUG org.pf4j.AbstractPluginManager - Creating wrapper for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.258 [main] DEBUG org.pf4j.AbstractPluginManager - Created wrapper 'PluginWrapper [descriptor=PluginDescriptor [pluginId=plugin2, pluginClass=com.hushow.plugin.demo.Plugin2, version=0.0.1, provider=hushowly, dependencies=[], description=, requires=*, license=null], pluginPath=plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar]' for plugin 'plugins/pf4j-demo-plugin2-0.0.1-SNAPSHOT.jar'
17:12:28.259 [main] DEBUG org.pf4j.DependencyResolver - Graph:
plugin1 -> []
plugin2 -> []
17:12:28.259 [main] DEBUG org.pf4j.DependencyResolver - Plugins order: [plugin1, plugin2]
17:12:28.260 [main] INFO org.pf4j.AbstractPluginManager - Plugin 'plugin1@0.0.1' resolved
17:12:28.261 [main] INFO org.pf4j.AbstractPluginManager - Plugin 'plugin2@0.0.1' resolved
17:12:28.262 [main] INFO org.pf4j.AbstractPluginManager - Start plugin 'plugin1@0.0.1'
17:12:28.262 [main] DEBUG org.pf4j.DefaultPluginFactory - Create instance for plugin 'com.hushow.plugin.demo.Plugin1'
17:12:28.265 [main] INFO com.hushow.plugin.demo.Plugin1 - plugin1 plugin started
17:12:28.265 [main] INFO org.pf4j.AbstractPluginManager - Start plugin 'plugin2@0.0.1'
17:12:28.265 [main] DEBUG org.pf4j.DefaultPluginFactory - Create instance for plugin 'com.hushow.plugin.demo.Plugin2'
17:12:28.266 [main] INFO com.hushow.plugin.demo.Plugin2 - plugin2 plugin started
17:12:28.269 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint'
17:12:28.269 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storages from classpath
17:12:28.269 [main] DEBUG org.pf4j.LegacyExtensionFinder - Read 'file:/home/hushow/wk-tmp/learning-demo/spring-plugin-demo/spring-pf4j-demo/spring-pf4j-main/target/spring-pf4j-main-0.0.1-SNAPSHOT.jar!/META-INF/extensions.idx'
17:12:28.272 [main] DEBUG org.pf4j.LegacyExtensionFinder - Read 'file:/home/hushow/wk-tmp/learning-demo/spring-plugin-demo/spring-pf4j-demo/spring-pf4j-main/target/spring-pf4j-main-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/spring-pf4j-api-0.0.1-SNAPSHOT.jar!/META-INF/extensions.idx'
17:12:28.273 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found possible 1 extensions:
17:12:28.273 [main] DEBUG org.pf4j.AbstractExtensionFinder - com.hushow.plugin.demo.DefaultExtensionPoint
17:12:28.273 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storages from plugins
17:12:28.273 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storage from plugin 'plugin1'
17:12:28.273 [main] DEBUG org.pf4j.LegacyExtensionFinder - Read 'META-INF/extensions.idx'
17:12:28.274 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found possible 1 extensions:
17:12:28.274 [main] DEBUG org.pf4j.AbstractExtensionFinder - com.hushow.plugin.demo.Plugin1ExtensionPointImpl
17:12:28.274 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storage from plugin 'plugin2'
17:12:28.274 [main] DEBUG org.pf4j.LegacyExtensionFinder - Read 'META-INF/extensions.idx'
17:12:28.275 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found possible 1 extensions:
17:12:28.275 [main] DEBUG org.pf4j.AbstractExtensionFinder - com.hushow.plugin.demo.Plugin2ExtensionPointImpl
17:12:28.275 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint' for plugin 'null'
17:12:28.276 [main] DEBUG org.pf4j.AbstractExtensionFinder - Loading class 'com.hushow.plugin.demo.DefaultExtensionPoint' using class loader 'org.springframework.boot.loader.LaunchedURLClassLoader@2e0fa5d3'
17:12:28.276 [main] DEBUG org.pf4j.AbstractExtensionFinder - Checking extension type 'com.hushow.plugin.demo.DefaultExtensionPoint'
17:12:28.293 [main] DEBUG org.pf4j.AbstractExtensionFinder - Added extension 'com.hushow.plugin.demo.DefaultExtensionPoint' with ordinal 0
17:12:28.293 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 1 extensions for extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint'
17:12:28.294 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint' for plugin 'plugin1'
17:12:28.294 [main] DEBUG org.pf4j.AbstractExtensionFinder - Loading class 'com.hushow.plugin.demo.Plugin1ExtensionPointImpl' using class loader 'org.pf4j.PluginClassLoader@3b764bce'
17:12:28.294 [main] DEBUG org.pf4j.AbstractExtensionFinder - Checking extension type 'com.hushow.plugin.demo.Plugin1ExtensionPointImpl'
17:12:28.295 [main] DEBUG org.pf4j.AbstractExtensionFinder - Added extension 'com.hushow.plugin.demo.Plugin1ExtensionPointImpl' with ordinal 0
17:12:28.295 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 1 extensions for extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint'
17:12:28.295 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint' for plugin 'plugin2'
17:12:28.295 [main] DEBUG org.pf4j.AbstractExtensionFinder - Loading class 'com.hushow.plugin.demo.Plugin2ExtensionPointImpl' using class loader 'org.pf4j.PluginClassLoader@45fe3ee3'
17:12:28.295 [main] DEBUG org.pf4j.AbstractExtensionFinder - Checking extension type 'com.hushow.plugin.demo.Plugin2ExtensionPointImpl'
17:12:28.296 [main] DEBUG org.pf4j.AbstractExtensionFinder - Added extension 'com.hushow.plugin.demo.Plugin2ExtensionPointImpl' with ordinal 0
17:12:28.296 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 1 extensions for extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint'
17:12:28.296 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 3 extensions for extension point 'com.hushow.plugin.demo.service.IDemoExtensionPoint'
17:12:28.296 [main] DEBUG org.pf4j.DefaultExtensionFactory - Create instance for extension 'com.hushow.plugin.demo.DefaultExtensionPoint'
17:12:28.296 [main] DEBUG org.pf4j.DefaultExtensionFactory - Create instance for extension 'com.hushow.plugin.demo.Plugin1ExtensionPointImpl'
17:12:28.296 [main] DEBUG org.pf4j.DefaultExtensionFactory - Create instance for extension 'com.hushow.plugin.demo.Plugin2ExtensionPointImpl'
true
1000
Plugin2ExtensionPointImpl getByid:11
17:12:28.297 [main] INFO org.pf4j.AbstractPluginManager - Stop plugin 'plugin2@0.0.1'
17:12:28.297 [main] INFO com.hushow.plugin.demo.Plugin2 - plugin2 plugin stopped
17:12:28.297 [main] INFO org.pf4j.AbstractPluginManager - Stop plugin 'plugin1@0.0.1'
17:12:28.297 [main] INFO com.hushow.plugin.demo.Plugin1 - plugin1 plugin stopped
17:12:28.297 [main] DEBUG org.pf4j.AbstractPluginManager - Already stopped plugin 'plugin1@0.0.1'
17:12:28.297 [main] INFO org.pf4j.AbstractPluginManager - Unload plugin 'plugin1@0.0.1'
17:12:28.298 [main] DEBUG org.pf4j.AbstractPluginManager - Already stopped plugin 'plugin2@0.0.1'
17:12:28.299 [main] INFO org.pf4j.AbstractPluginManager - Unload plugin 'plugin2@0.0.1'
2.9 依赖配置 (pom.xml示例)
主应用程序的依赖:
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.hushow.plugin.demo</groupId>
<artifactId>spring-pf4j-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
插件的依赖:
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.hushow.plugin.demo</groupId>
<artifactId>spring-pf4j-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
这样你就完成了一个完整的PF4J扩展点示例。你可以根据需要扩展这个基础框架,添加更多功能如插件配置、生命周期管理等。