PF4J:轻量级 Java 插件框架

Plugin Framework for Java

插件是第三方扩展系统应用功能的一种方式。

插件实现由应用程序或其他插件声明的扩展点。

插件还可以定义扩展点。

使用 PF4J,您可以轻松地将单体 Java 应用程序转换为模块化应用程序。

当前构建 PF4J 所需的最低 Java 版本应该是 9,但运行时 Java 版本可以是 8,因为该工件是一个多版本 jar。

组件:

  • Plugin: 所有插件类型的基类。每个插件都加载到单独的类加载器中以避免冲突
  • PluginManager: 用于插件管理的各个方面(loading, starting, stopping)。您可以使用内置实现作为 DefaultPluginManager,也可以从 AbstractPluginManager 开始实现自定义插件管理器(仅实现工厂方法)
  • PluginLoader: 加载插件所需的所有信息(classes)
  • ExtensionPoint: 应用程序中可以调用自定义代码的点。它是一个java接口标记。任何java接口或抽象类都可以被标记为扩展点(实现ExtensionPoint接口)。
  • Extension: 扩展点的实现。这是一个类上的java注解。
  • PluginStateListener:

DefaultExtensionFinder 在所有扩展索引文件 META-INF/extensions.idx 中查找扩展。 PF4J 使用 Java 注解处理在编译时处理所有用 @Extension 注解的类并生成扩展索引文件。

插件存储在一个文件夹中。您可以在 DefaultPluginManager 的构造函数中指定插件文件夹。如果未指定插件文件夹,则由 System.getProperty("pf4j.pluginsDir", "plugins") 返回位置。

注意:“pf4j.pluginsDir”属性附带逗号分隔的目录列表支持(支持多个插件根目录)。

未生成 extensions.idx ,需要配置一下Maven

<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>

打包:支持 zip、jar(推荐) 格式

如果您不需要在运行时在应用程序中加载/卸载 Java 代码的某些部分,则根本没有必要使用插件。您也可以仅使用扩展并将编译的类放入应用程序类路径(所谓的系统扩展)。

编写原数据

  1. META-INF/MANIFEST.MF 或者 maven-jar-plugin、maven-assembly-plugin(plugin.properties)
plugin.class=org.pf4j.demo.welcome.WelcomePlugin
plugin.id=welcome-plugin
plugin.version=0.0.1
plugin.requires=1.0.0
#依赖插件版本
plugin.dependencies=x, y, z
plugin.description=My example plugin
plugin.provider=Decebal Suiu
plugin.license=Apache License 2.0

流程

demo:https://github.com/pf4j/pf4j/tree/master/demo

  1. 定义可扩展接口(Plugin-Api)
  2. 实现插件(Plugins)
  3. 插件打包(Package)
  4. 加载执行插件(Plugin-App)

生命周期

主要插件状态是:

  • CREATED
  • DISABLED
  • RESOLVED
  • STARTED
  • STOPPED

DefaultPluginManager 包含以下逻辑:

  • 所有插件均已解析并加载
  • DISABLED 插件不会由 startPlugins() 中的 pf4j 自动 STARTED ,但您可以通过调用 startPlugin(pluginId) 手动启动(并因此启用)已禁用的插件而不是 enablePlugin(pluginId) + startPlugin(pluginId)
  • 只有 STARTED 插件可以提供扩展。任何其他状态都不应该被视为准备好为正在运行的系统提供扩展。

DISABLED 插件和 STARTED 插件之间的区别是:

  • STARTED 插件已执行 Plugin.start()DISABLED 插件尚未执行
  • STARTED 插件可能会提供扩展实例, DISABLED 插件可能不会

自定义插件管理器

要创建自定义插件管理器,您必须选择以下选项之一:

  • 实现 PluginManager 接口(从头开始创建插件管理器)
  • 修改内置实现的某些方面/行为( DefaultPluginManager
  • 扩展 AbstractPluginManager

开发模式

  • DEVELOPMENT:模式是插件创建的标准工作流程:为每个插件创建一个新的 Maven 模块,编码插件(声明新的扩展点和/或添加新的扩展),将插件打包在 zip 文件中,部署 zip文件到插件文件夹。这些操作非常耗时,因此我引入了DEVELOPMENT运行时模式。
  • DEPLOYMENT:模式的主要优点是他/她不会被迫打包和部署插件。在开发模式下,您可以以简单快速的方式开发插件。

Extension

  • ordinal:扩展加载顺序
  • points:扩展点类列表
  • plugins:依赖插件列表
public @interface Extension {

    /**
     * 扩展的顺序。
     * 序号用于对扩展名进行排序。
     *
     * @return 扩展的顺序
     */
    int ordinal() default 0;

    /**
     * 由此扩展实现的扩展点数组。
     * 此显式配置将覆盖 org.pf4j.processor.ExtensionAnnotationProcessor 中扩展点的自动检测。
     * 
     * 如果扩展直接派生自扩展点,则不需要此属性。但在某些 更复杂的方案 https://github.com/pf4j/pf4j/issues/264 中,显式设置扩展的扩展点可能很有用。
     * @return 由此扩展实现的扩展点类
     */
    Class<? extends ExtensionPoint>[] points() default {};

    /**
     * 插件 ID 数组,必须可用才能加载此扩展。
     * AbstractExtensionFinder 如果这些插件在运行时不可用/ 启动,则不会加载此扩展。
     * 注意:此功能要求可选的 ASM 库 在应用程序类路径上可用,并且必须通过 AbstractExtensionFinder. setCheckForExtensionDependencies(boolean)显式启用。
     *
     * @return 插件 ID,必须可用才能加载此扩展
     */
    String[] plugins() default {};

}

实例化:ExtensionFactory

当调用 plugin.getExtensions(MyExtensionPoint.class) 时,将根据需要创建扩展实例。默认情况下,如果您调用 plugin.getExtensions(MyExtensionPoint.class) 两次:

然后对于每个调用,都会创建一个新的扩展实例。

如果要返回相同的扩展实例(单例),则需要使用SingletonExtensionFactory:

常见问题

  • No Extensions Found

如果文件 extensions.idx 不存在,则注释处理步骤可能有问题(在 IDE 或 Maven 脚本中启用注释处理)。

如果文件 extensions.idx 存在,并且它不为空,那么肯定存在类加载器问题(在两个不同的类加载器中具有相同的扩展点),在这种情况下,您必须删除一些库(可能是 API jar )来自插件。

如果问题仍然存在,或者您想查找与扩展发现过程相关的更多信息(例如,每个插件加载哪些接口/类,哪些类不被识别为扩展点的扩展),那么您必须放置 TRACE 级别 PluginClassLoaderAbstractExtensionFinder 的记录器(请参阅 log4j2.properties 文件进行演示)。