PF4J ExtensionPoint核心原理与实战示例

2025-04-06 93 0

1 介绍

PF4J(Plugin Framework for Java)是一款轻量级Java插件框架,通过ExtensionPoint机制实现了松耦合的插件扩展,支持热插拔、隔离、插件的生命周期管理等能力

https://github.com/pf4j/pf4j

1.1 优点:
  • 低侵入性

    public interface DataProcessor extends ExtensionPoint {
    String process(String input);
    }
  • 动态加载与热插拔

    pluginManager.loadPlugin(pluginId);  // 动态加载
    pluginManager.startPlugin(pluginId); // 激活插件
  • 依赖隔离
    每个插件使用独立的ClassLoader,避免类冲突,尤其适合多团队协作开发。

  • 灵活的生命周期管理
    提供start()、stop()、delete()等钩子方法,方便管理插件资源。

  • 支持Spring Framework集成

    https://github.com/pf4j/pf4j-spring

1.2 缺点:
  • 泛型支持有限
    // 警告:Raw use of parameterized class 'ExtensionPoint'
    List<ExtensionPoint> extensions = pluginManager.getExtensions(ExtensionPoint.class);
  • 性能开销
    频繁加载/卸载插件可能引发内存泄漏,需谨慎管理生命周期。
1.3 原理:

file

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 运行步骤
  1. 构建所有模块:

    mvn clean package
  2. 创建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/
  3. 运行主应用程序:

    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扩展点示例。你可以根据需要扩展这个基础框架,添加更多功能如插件配置、生命周期管理等。

相关文章

轻量级微服务监控方案:Spring Boot Admin+Cloud+Nacos
当Actuator失效时:Tomcat线程池监控的全面解决方案
快速实现通用的办公文档在线预览方案
Spring Feign大文件上传踩坑记
MinIO分布式存储方案预研
Dubbo+Grpc+Spring Boot初体验

发布评论