跳到内容

插件系统

受众:插件开发者、二次开发者 摘要:Plugin starter 首段提供静态插件元数据发现和本地生命周期 contract。它不是热加载框架,也不替代现有 Provider / SPI。

定位

spring-open-starter-plugin 用来让应用识别本地安装、classpath 描述文件或插件贡献者声明的插件元数据,并形成可查询、可审计、可文档化的插件注册表。spring-open-core-plugin 在此之上提供后台插件治理闭环:状态持久化、启停、安装 / 升级 / 卸载计划、生命周期执行记录和 Admin 管理页面。

首段只做五件事:

  • 读取 spring-open-plugin.yml / spring-open-plugin.propertiesMETA-INF/spring-open/plugins/* 描述文件,或通过 PluginContributor 贡献的描述信息。
  • 注册 PluginManager,供应用、后台或运维页查询插件列表、状态矩阵和生命周期计划。
  • 约定插件可声明能力、扩展点、配置 key、权限码和资源入口。
  • 约定本地插件安装、升级、降级、卸载的迁移脚本和回滚脚本,并生成可审计计划。
  • 提供 PluginLifecycleExecutorPluginLifecycleScriptRunner SPI,在应用显式调用时按计划执行脚本并记录回滚结果;Core Plugin 会把执行结果落库供后台审计。

暂不做动态 jar 热加载、远程插件市场、插件沙箱、内置 SQL / 文件执行器、资源自动复制或独立 ClassLoader。插件状态和生命周期审计由 Core Plugin 持久化;插件真实业务 Bean、菜单、权限和 Provider 仍由对应模块或 Service Provider 管理。

配置

配置默认值说明
spring.open.plugin.enabledtrue是否启用插件元数据发现
spring.open.plugin.locationsclasspath 默认描述文件位置插件描述文件 resource pattern,支持 classpath*:file:
spring.open.plugin.fail-on-invalid-descriptortrue描述文件缺少必填字段、解析失败或插件 id 重复时是否启动失败
spring.open.plugin.framework-version当前应用框架版本,用于插件兼容矩阵检查;为空时不限制
spring.open.plugin.states.<plugin-id>.enabled描述文件默认态单个插件启用状态覆盖

默认扫描位置:

  • classpath*:META-INF/spring-open/plugins/*.properties
  • classpath*:META-INF/spring-open/plugins/*.yml
  • classpath*:META-INF/spring-open/plugins/*.yaml
  • classpath*:spring-open-plugin.properties
  • classpath*:spring-open-plugin.yml
  • classpath*:spring-open-plugin.yaml

本地安装目录可以这样接入:

yaml
spring:
  open:
    plugin:
      locations:
        - file:plugins/*.yml
        - file:plugins/*.properties

描述文件

Properties 示例:

properties
plugin.id=sample-provider
plugin.name=Sample Provider
plugin.version=1.0.0
plugin.description=示例插件
plugin.vendor=ExampleVendor
plugin.homepage=https://example.com/plugins/sample-provider
plugin.enabled-by-default=true
plugin.capabilities=payment-provider,admin-view
plugin.extension-points[0]=payment.provider
plugin.extension-points[1]=admin.menu
plugin.dependencies[0]=base-provider
plugin.compatible-framework-versions[0]=0.7.*
plugin.config-keys[0]=spring.open.payment.providers.sample
plugin.permissions[0]=plugin:sample:view
plugin.resources[0]=classpath:/sample-provider/assets
plugin.install-scripts[0]=classpath:/sample-provider/install.sql
plugin.upgrade-scripts[0]=classpath:/sample-provider/upgrade-1.0.0.sql
plugin.downgrade-scripts[0]=classpath:/sample-provider/downgrade-0.9.0.sql
plugin.uninstall-scripts[0]=classpath:/sample-provider/uninstall.sql
plugin.rollback-scripts[0]=classpath:/sample-provider/rollback.sql

YAML 示例:

yaml
plugin:
  id: sample-provider
  name: Sample Provider
  version: 1.0.0
  description: 示例插件
  vendor: ExampleVendor
  homepage: https://example.com/plugins/sample-provider
  enabled-by-default: true
  capabilities:
    - payment-provider
    - admin-view
  extension-points:
    - payment.provider
    - admin.menu
  dependencies:
    - base-provider
  compatible-framework-versions:
    - 0.7.*
  config-keys:
    - spring.open.payment.providers.sample
  permissions:
    - plugin:sample:view
  resources:
    - classpath:/sample-provider/assets
  install-scripts:
    - classpath:/sample-provider/install.sql
  upgrade-scripts:
    - classpath:/sample-provider/upgrade-1.0.0.sql
  downgrade-scripts:
    - classpath:/sample-provider/downgrade-0.9.0.sql
  uninstall-scripts:
    - classpath:/sample-provider/uninstall.sql
  rollback-scripts:
    - classpath:/sample-provider/rollback.sql

plugin.id 是必填项,建议使用 kebab-case。未配置 name 时展示名回退到 id,未配置 version 时回退为 0.0.0

Java contract

类型说明
PluginDescriptor插件元数据:id、名称、版本、能力、扩展点、配置 key、权限和资源
PluginContributor插件元数据贡献 SPI,推荐通过 @AutoService(PluginContributor.class) 注册,也兼容 META-INF/spring.factories
PluginDescriptorLoader描述加载扩展点;默认组合资源描述文件、ServiceLoader 贡献和 Spring factories 贡献,可替换为自有安装目录或配置中心来源
PluginStateRegistry插件状态注册表,负责提供插件启停状态、状态来源和当前框架版本;默认从 PluginProperties 读取
PluginStatus插件状态视图,包含启停状态、状态来源、依赖检查明细和版本兼容矩阵
PluginLifecyclePlan插件安装、升级、降级或卸载的可审计执行计划,包含动作、版本、脚本、资源和提示
PluginLifecycleExecutor生命周期计划执行入口,按计划顺序执行脚本,失败时执行回滚脚本,并返回可审计结果
PluginLifecycleScriptRunner生命周期脚本执行 SPI;应用按脚本 URI 或内部指令协议注册 Runner
PluginLifecycleExecutionResult生命周期执行结果,包含成功状态、已执行脚本、已回滚脚本、失败脚本、回滚失败脚本、资源入口、提示和摘要
PluginManager插件元数据管理入口,提供 list()find(id)exists(id)statuses()status(id)planInstall()planUpgrade()planUninstall()
DatabasePluginStateRegistryCore Plugin 默认注册的数据库状态注册表,优先读取后台持久化状态,配置文件状态作为兜底

状态和兼容检查

Plugin starter 会通过 PluginStateRegistry 根据描述文件和配置计算插件状态:

  • ENABLED:插件启用,并通过依赖与兼容矩阵检查。
  • DISABLED:插件被描述文件默认态或配置覆盖禁用。
  • BLOCKED:插件依赖缺失,或依赖插件未处于可用状态。
  • INCOMPATIBLE:插件声明的 compatible-framework-versions 不包含当前 framework-version

compatible-framework-versions 支持精确版本、*0.7.* 这类前缀通配。

状态结果会保留可解释字段:

  • stateSourceDESCRIPTOR_DEFAULT 表示来自描述文件默认态,CONFIGURED 表示来自 spring.open.plugin.states.<plugin-id>.enabled,自定义状态注册表可返回 CUSTOM_REGISTRY 或自定义来源。
  • frameworkVersion:本次兼容矩阵检查使用的框架版本,来自 PluginStateRegistry.frameworkVersion()
  • dependencyChecks:逐项返回依赖插件是否存在、依赖状态、是否可用和阻塞原因。
  • compatibilityChecks:逐项返回框架版本、匹配模式、是否兼容和兼容问题;未声明兼容版本时会以 * 作为默认兼容矩阵行。

插件真实贡献 Provider、监听器或菜单时,仍要走对应 starter / 业务模块已有扩展点。Plugin starter 只记录“插件声明了什么”,不越过既有边界直接装载实现。

Java SPI 贡献

插件包如果不希望额外维护描述文件,可以实现 PluginContributor

java
import com.google.auto.service.AutoService;
import com.springopen.starter.plugin.PluginContributor;
import com.springopen.starter.plugin.PluginDescriptor;

import java.util.List;

@AutoService(PluginContributor.class)
public class SamplePluginContributor implements PluginContributor {

    @Override
    public List<PluginDescriptor> descriptors() {
        return List.of(PluginDescriptor.builder()
                .id("sample-provider")
                .name("Sample Provider")
                .version("1.0.0")
                .capabilities(List.of("payment-provider"))
                .extensionPoints(List.of("payment.provider"))
                .build());
    }

}

推荐使用 @AutoService 生成标准 META-INF/services/com.springopen.starter.plugin.PluginContributor,运行时由 Java ServiceLoader 发现。无法启用注解处理或需要显式接入 Spring factories 的外部包,也可以使用:

properties
# META-INF/spring.factories
com.springopen.starter.plugin.PluginContributor=\
com.example.plugins.SamplePluginContributor

默认 loader 会按“资源描述文件 → ServiceLoader 贡献 → Spring factories 贡献”的顺序合并,并按贡献者类名去重。plugin.id 必须全局唯一;重复 id、缺少 id 或贡献者执行失败时,默认遵循 spring.open.plugin.fail-on-invalid-descriptor=true 直接启动失败。关闭该配置时会跳过坏来源或保留先出现的同 id 描述。

安装升级计划

Plugin starter 不直接写文件、不内置 SQL 执行器、不加载外部 jar;它根据当前 registry 和目标描述文件生成生命周期计划,并提供显式执行入口,方便后台、安装器或运维流程在受控事务中接入真实 Runner。

方法场景计划动作
planInstall(targetDescriptor)当前 registry 中不存在目标插件INSTALL,返回 install-scriptsrollback-scriptsresources
planInstall(targetDescriptor)当前 registry 已存在同名插件NOOP,提示插件已安装
planUpgrade(targetDescriptor)当前版本低于目标版本UPGRADE,返回 upgrade-scripts
planUpgrade(targetDescriptor)当前版本高于目标版本DOWNGRADE,返回 downgrade-scripts
planUpgrade(targetDescriptor)当前版本等于目标版本NOOP
planUpgrade(targetDescriptor)当前 registry 中不存在目标插件INSTALL,附带“未安装,使用安装计划”的提示
planUninstall(id)当前 registry 中存在插件UNINSTALL,返回 uninstall-scriptsrollback-scripts
planUninstall(id)当前 registry 中不存在插件NOOP

计划中的 warnings 会包含目标插件依赖缺失和框架版本兼容提示。

执行生命周期计划时调用 PluginLifecycleExecutor.execute(plan)

  • NOOP 或没有脚本的计划会成功返回,且不会触发任何 Runner。
  • 非空 migrationScripts 会按声明顺序查找支持该脚本入口的 PluginLifecycleScriptRunner 并执行。
  • 任一迁移脚本失败时,执行器会按计划中的 rollbackScripts 顺序尝试补偿,并在 PluginLifecycleExecutionResult.rollbackScripts 中记录已成功执行的回滚脚本。
  • 失败结果会结构化返回 failedScript / failureMessage;如果补偿脚本也失败,会额外返回 rollbackFailedScript / rollbackFailureMessage,便于后台审计、人工重试和告警定位。
  • 没有 Runner 支持某个脚本时,执行结果为失败,不会静默当作成功。
  • resources 只作为资源入口回传,starter 不自动复制资源、不写权限菜单种子、不写审计日志;这些仍由接入方或后续业务功能簇在受控事务中处理。

Core Plugin 管理 API

spring-open-core-plugin 在检测到 PluginManager Bean 时会暴露插件管理 API,用于后台或运维页治理当前本地插件注册表。它只治理已经在 classpath、描述文件或 PluginContributor 中声明的插件,不下载远程插件,也不动态加载外部 jar。

方法路径权限说明
GET/api/v1/admin/pluginsplugin:view查询插件状态列表,包含描述信息、状态来源、启用结果、依赖检查和版本兼容矩阵
GET/api/v1/admin/plugins/{id}plugin:view按插件 ID 查询单个插件状态详情
GET/api/v1/admin/plugins/{id}/config/schemaplugin:view查询插件描述符声明的配置 key,并转换为只读动态字段 Schema 元数据;不返回配置值
POST/api/v1/admin/plugins/{id}/enableplugin:state:update持久化启用插件,后续状态查询立即使用数据库覆盖态
POST/api/v1/admin/plugins/{id}/disableplugin:state:update持久化停用插件,依赖它的插件会在状态矩阵中显示阻塞
GET/api/v1/admin/plugins/{id}/install-planplugin:view生成安装计划,只返回脚本、资源和风险提示,不执行
GET/api/v1/admin/plugins/{id}/upgrade-planplugin:view生成升级计划,只返回脚本、资源和风险提示,不执行
GET/api/v1/admin/plugins/{id}/uninstall-planplugin:view生成卸载计划,只返回脚本、资源和风险提示,不执行
POST/api/v1/admin/plugins/{id}/installplugin:lifecycle:execute执行安装计划并写入生命周期执行记录,operationKey 支持幂等
POST/api/v1/admin/plugins/{id}/upgradeplugin:lifecycle:execute执行升级计划并写入生命周期执行记录,operationKey 支持幂等
POST/api/v1/admin/plugins/{id}/uninstallplugin:lifecycle:execute执行卸载计划,成功后持久化为未安装 / 停用
GET/api/v1/admin/plugins/executionsplugin:execution:view查询最近生命周期执行记录,可按插件 ID 过滤

返回的 state 取值与 PluginState 一致:ENABLEDDISABLEDBLOCKEDINCOMPATIBLE

列表和详情都会返回 installedinstalledVersionstateSourceframeworkVersionstateReasonoperationKeydependencyCheckscompatibilityChecksdependencyIssuescompatibilityIssues,后台可直接展示“是否安装、状态来自哪里、依赖哪项阻塞、哪个版本模式未匹配”。

生命周期执行记录会保存动作、版本、成功状态、已执行脚本、回滚脚本、资源、提示、失败原因、操作人、原因和 operationKey。执行成功后,安装 / 升级会把插件标记为已安装并启用,卸载会把插件标记为未安装并停用。

Core Plugin 不内置 SQL Runner 或文件复制 Runner。生产环境要真正执行脚本时,应由插件包或部署侧注册 PluginLifecycleScriptRunner,并只支持受控 URI 协议或内部指令协议,避免把任意文件路径 / SQL 文本暴露给后台直接执行。

Admin 插件管理页

后台菜单内置 /plugin 插件管理页,使用 plugin:viewplugin:state:updateplugin:lifecycle:executeplugin:execution:view 权限治理 Core Plugin API。

该页面支持:

  • 查看已发现插件的展示名、插件 ID、版本、厂商、安装状态、启停状态、状态来源、默认启用标记、框架版本、能力、扩展点、权限码、配置 key、资源入口、依赖检查明细和版本兼容矩阵。
  • 在插件详情中通过 Schema 动态字段组件展示配置 key 声明;该展示只说明插件需要哪些配置,不读取或回显运行期配置值。
  • 启用 / 停用本地插件状态,并记录原因和操作 key。
  • 查看安装 / 升级 / 卸载计划,提前确认迁移脚本、回滚脚本、资源入口和风险提示。
  • 执行安装 / 升级 / 卸载,并查看最近生命周期执行记录、失败原因和回滚结果。

页面不下载远程插件、不加载外部 jar、不自动发布菜单 / 权限 / 静态资源;这些能力仍由对应业务模块的迁移、Service Provider 和部署流程负责。

边界

  • 不让业务模块直接依赖插件实现类。
  • 不把 Provider / SPI 复制一套插件专用模型。
  • PluginContributor 只贡献元数据,不动态创建业务 Bean;真实 Provider / Bean 仍走对应 starter、生命周期 Provider 或 Spring Boot 自动装配。
  • 不引入动态类加载、远程市场或签名校验;本地插件包的来源可信、签名验收和交付流程由发布 / 部署 runbook 负责。
  • 后台启停、安装 / 卸载操作入口和生命周期审计已经由 Core Plugin 提供;资源发布、插件专属权限菜单种子和外部安全扫描仍归插件所属模块或部署流程。

Released under the Apache License 2.0.