# dynamic-plugin **Repository Path**: artislong/dynamic-plugin ## Basic Information - **Project Name**: dynamic-plugin - **Description**: Dynamic Plugin 是一个灵活的动态插件加载平台,支持在运行时动态加载、卸载插件,无需重启应用。该系统适用于需要热插拔功能模块的 Java 应用,支持多种框架集成。 - **Primary Language**: Java - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-08-28 - **Last Updated**: 2026-09-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Dynamic Plugin [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Maven Central](https://img.shields.io/maven-central/v/com.artislong/dynamic-plugin.svg)](https://search.maven.org/search?q=g:com.artislong%20AND%20a:dynamic-plugin) [![Java Version](https://img.shields.io/badge/Java-17%2B-blue)](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.11-6DB33F)](https://spring.io/projects/spring-boot) Dynamic Plugin 是一个灵活的动态插件加载平台,支持在运行时动态加载、卸载插件,无需重启应用。系统适用于需要热插拔功能模块的 Java 应用,支持多种框架集成(Spring、MyBatis-Plus、XXL-Job、ElasticJob、RocketMQ 等)。 > 核心设计:将**接口 / 抽象类 / 普通类**剥离到外挂的契约包 `plugin-contract.jar` 中——业务系统与插件实现方共同依赖它;插件实现方据此编写实现并打成 jar,由平台在运行时加载、挂载,并通过 API 按契约调用。可选引入**内存沙箱**对插件执行进行隔离与资源限制。 ## 核心特性 - **动态加载 / 卸载**:支持运行时加载 JAR 包、卸载并释放资源,无需重启应用 - **契约式插件化**:接口 / 抽象类 / 普通类外置为契约包,业务侧与插件实现侧解耦 - **多数据源**:支持从本地文件系统、OSS、SFTP 等多种数据源加载插件 - **内存沙箱(隔离执行)**:可选引入 sandbox,对插件方法调用施加类加载器隔离、类路径白名单、内存 / 线程 / CPU 等资源限制、异常隔离 - **框架集成**:支持与 Spring、MyBatis-Plus、XXL-Job、ElasticJob、RocketMQ 等框架集成 - **集群同步**:支持集群环境下插件状态同步 - **管理平台**:提供插件管理界面,支持插件的增删改查、版本管理 ## 插件方法命名建议 插件的核心调用单元是"**某个契约类(接口 / 抽象类 / 普通类)的某一个方法**",而不是 jar 包,也不是某个实现类的实例。平台在运行时基于被调用的 `Method` 自动派生该方法标识,**无需也不建议单独设置 pluginId**: - **可读标识**(仅用于调试 / 核对 / 写配置):`契约类完整类路径 + "#" + 方法名 + "(" + 参数类型列表 + ")"`,例如 `com.artislong.GreetingService#sayHello(java.lang.String)`。 - **运行时 key(默认)**:对上述可读标识取 `hashCode()` 得到紧凑串 `p_`(如 `p_1a2b3c4d`),作为 registry 与配置存储的实际 key。可读标识偏长且含包名 / 泛型,不适合做 key;紧凑串进程内稳定、节省内存。跨进程持久化场景可改用 `PluginIdUtil.resolveHash256`(SHA-256 截断)。 - 可读串与紧凑 key 通过 `PluginIdUtil.resolve` / `describe` 互转;调试期用 `SandboxDebug.dumpPluginIds()` 打印全部 `可读串 → 紧凑key` 映射。 基于此机制,给出命名与定义建议: 1. **不要使用"同名但语义不同的方法"**:同名方法(含重载)只能靠参数类型列表区分,标识虽可精确区分,但可读性差、配置 key 不直观,容易引发误解与误操作。插件契约应让每个方法名都能表达其唯一职责。 2. **重载可以存在,但要谨慎**:若确有重载(如 `add(int,int)` 与 `add(java.util.List)`),它们会被识别为两个不同的方法标识、可分别隔离与配置;请务必在契约文档中标注每个重载的语义边界。 3. **方法即边界**:沙箱策略(档位、内存 / 线程 / CPU / 类路径限制)最终落到"方法标识"粒度(默认用紧凑 key),一个 jar 内含多个契约或同一契约多个方法,均自动获得相互独立的沙箱与策略。 4. **调用时无需关心 pluginId**:业务代码始终通过 `Plugin.getProxy().xxx(args)` 调用;走沙箱还是直跑、用哪一档沙箱,由依赖与配置决定(详见上文沙箱融合说明与 `docs/sandbox-api-fusion-design.md`)。 > 一句话:**插件方法的名字要能"顾名思义"且全局唯一,避免同名歧义**,这样自动派生的 method-level pluginId 才是清晰、可维护、可独立配置的。 ## 系统架构 下图展示 Dynamic Plugin 的插件化平台架构与完整运行逻辑:编译期契约(`plugin-contract.jar`)剥离接口 / 抽象类 / 普通类,插件实现方依赖契约包编写实现并打成 jar;业务系统通过 `admin` 上传暂存、经 `core` + `datasource` 从 OSS/SFTP/Local 下载加载(亦支持本地直接加载),再由 `api` 按契约调用实现,并在引入 `sandbox` 时于内存沙箱中隔离执行。 ![dynamic-plugin 系统架构图](docs/dynamic-plugin-architecture.svg) > 交互版(可缩放 / 查看清晰大图):[docs/dynamic-plugin-architecture.html](docs/dynamic-plugin-architecture.html) **图例说明** - 绿色(`admin` / `core` / `api`):业务系统基座核心模块 - 紫色(`datasource`):存储适配层,提供 OSS/SFTP/Local 下载读取 - 橙色(存储后端):OSS / SFTP / Local 三选一暂存 - 红色(`sandbox`):内存沙箱,按需引入,负责类加载器隔离 / 类路径白名单 / 内存限制 / 异常隔离 - 实线=正式加载/调用流程;虚线=本地捷径(跳过暂存与下载)/ 沙箱结果回传 - 分布式:admin 独立部署 + core 集群 + 共享 OSS/SFTP;单体:admin 内嵌 + core 内嵌 + 可用 Local ### 插件加载与运行流程 - **远程加载**:插件实现 jar → ① admin 上传 → ② 暂存到存储后端(OSS/SFTP/Local)→ ③ 触发 core 加载请求 → ④ core 经 datasource 下载 jar → ⑤ core 类加载并挂载到运行时 - **本地加载**:插件实现 jar(本地文件)→ 直接由 core 从 Local 加载,跳过 admin 暂存与 datasource 下载(虚线捷径) - **运行时调用**:业务代码 → `api` → 通过契约调用插件实现实例 → 判断「是否引入 sandbox?」: - 否 → 在主类加载器 / 正常 JVM 中直接执行 - 是 → `sandbox` 用自定义 ClassLoader 在内存构造隔离沙箱(类加载器隔离 / 类路径白名单 / 内存限制 / 异常隔离),执行后结果回传 api
查看旧版 ProcessOn 流程图(嵌入)
## 模块说明 | 模块 | 职责 | |---|---| | `dynamic-plugin-admin` | 插件管理端核心:上传 / 暂存插件 jar(OSS/SFTP/Local),触发加载请求,管理插件状态 | | `dynamic-plugin-admin-framework` | 管理端框架集成抽象层(含 `dynamic-plugin-admin-spring` 适配 Spring Boot) | | `dynamic-plugin-core` | 插件内核:接收加载请求、类加载、插件生命周期与实例管理、集群同步 | | `dynamic-plugin-api` | 插件 API 调用层:将接口 / 抽象类 / 普通类包装为插件,提供插件实例生命周期管理 API 与运行时调用入口(`Plugin` / `PluginManager` / `PluginInstanceLoader`) | | `dynamic-plugin-api-framework` | API 框架集成层:`java`(无开源框架时为插件实例生命周期管理)、`spring`(从 Spring 容器加载 Bean 作为插件实例) | | `dynamic-plugin-datasource` | 插件数据源适配层:提供 Oss / Sftp / Local 三种 jar 下载与读取能力 | | `dynamic-plugin-framework` | 框架集成层:`spring`、`xxljob`、`mybatisplus`、`elasticjob`、`rocketmq` 五大集成 | | `dynamic-plugin-sandbox` | 内存沙箱基座(接口 / 策略 / 抽象):提供类加载器隔离、资源限制、类路径白名单、异常隔离等能力 | | `dynamic-plugin-sandbox-jdk17` / `-jdk8` | 沙箱按 JDK 分叉的实现模块(JDK17 / JDK8 各自的 `SandboxFactory`) | | `dynamic-plugin-sample` | 示例项目:管理端、应用、插件、API、沙箱示例 | ### dynamic-plugin-admin `dynamic-plugin-admin` 是 Dynamic Plugin 的管理端核心模块,提供插件管理接口与存储后端交互能力。 **主要功能** - 上传插件 JAR 包并暂时存储到 OSS / SFTP / Local(三选一) - 触发执行器(core)的插件加载 / 卸载请求 - 维护插件元信息与版本管理 **使用场景** - 需要统一管控插件增删改查、版本的运维场景 > 分布式:admin 可独立部署,配合共享存储(OSS/SFTP)支撑多执行器集群;单体:admin 可内嵌到业务系统。 ### dynamic-plugin-admin-framework `dynamic-plugin-admin-framework` 是管理端的框架集成抽象层,下设: - `dynamic-plugin-admin-spring`:管理端在 Spring Boot 环境下的自动装配与接入适配 ### dynamic-plugin-core `dynamic-plugin-core` 是 Dynamic Plugin 的核心模块,提供插件加载、卸载的核心功能。 **主要功能** - 实现插件加载、卸载的核心逻辑 - 管理插件实例的生命周期(加载、挂载、卸载、资源释放) - 提供插件状态监控 - 集群环境下插件状态同步(与管理端对齐) **使用场景** - 需要动态加载插件的 Java 应用 - 需要扩展插件功能的框架集成 ### dynamic-plugin-api `dynamic-plugin-api` 是 Dynamic Plugin 的核心 API 模块,定义插件加载、卸载和管理的接口规范,并提供运行时调用入口。 **核心接口** - `PluginInstanceLoader`:插件实例加载器接口 - `PluginInstanceManager`:插件实例管理器接口(生命周期管理) - `Plugin`:插件代理入口,通过 `getProxy()` 调用实现类方法(与 sandbox 融合,默认无沙箱、可配置开启) **关键实现** - `DefaultPluginInstanceLoader`:默认插件加载器实现 - `DefaultPluginInstanceManager`:默认插件管理器实现 ### dynamic-plugin-api-framework `dynamic-plugin-api-framework` 是 API 的框架集成模块,提供从框架上下文取得 / 管理插件实例的能力: - `dynamic-plugin-api-framework-java`:无开源开发框架时,对插件实例做生命周期管理 - `dynamic-plugin-api-framework-spring`:从 Spring 容器加载 Bean 作为插件实例,监听 Bean 创建 / 销毁自动更新插件缓存 ### dynamic-plugin-datasource `dynamic-plugin-datasource` 是 Dynamic Plugin 的数据源模块,承载存储适配层,支持从多种数据源加载插件: - `dynamic-plugin-datasource-oss`:OSS 对象存储数据源 - `dynamic-plugin-datasource-sftp`:SFTP 文件传输数据源 **主要功能** - 提供 OSS / SFTP / Local 三种 jar 下载与读取能力,供 core 拉取插件 jar - 提供数据源扩展接口 `PluginDataSource` **使用场景** - 需要从不同数据源加载插件的场景 ### dynamic-plugin-framework `dynamic-plugin-framework` 是框架集成层,下设: - `dynamic-plugin-framework-spring`:Spring 集成(自动装配、Bean 生命周期桥接) - `dynamic-plugin-framework-xxljob`:XXL-Job 集成(2.5.0) - `dynamic-plugin-framework-mybatisplus`:MyBatis-Plus 集成(3.5.17) - `dynamic-plugin-framework-elasticjob`:ElasticJob 集成(3.0.4) - `dynamic-plugin-framework-rocketmq`:RocketMQ 集成(2.3.4) ### dynamic-plugin-sandbox(内存沙箱) `dynamic-plugin-sandbox` 是**内存沙箱基座**:定义沙箱的接口、策略(`SandboxPolicy`)、抽象与资源限制模型(类加载器隔离 / 类路径白名单 / 内存 / 线程 / CPU / 文件 / 网络)。具体按 JDK 版本的实现分别在: - `dynamic-plugin-sandbox-jdk17`:JDK 17 实现的 `Jdk17SandboxFactory`(当前 jdk17 分支默认启用) - `dynamic-plugin-sandbox-jdk8`:JDK 8 实现的 `Jdk8SandboxFactory`(jdk8 分支启用) **与 API 的融合(按需引入)** - 仅引入 `dynamic-plugin-api`(不引 sandbox):插件方法在**当前环境直接执行**,无沙箱 - 引入 api + sandbox 并将融合开关置为开启:插件方法调用**默认走沙箱**(basic 兜底),可通过配置调档(basic / enhanced / strict)与限制资源;也支持代码手动创建独立沙箱 - 沙箱以 **方法(契约类 + 方法名 + 参数类型)** 为隔离单元:`pluginId` 由框架基于被调用方法**自动派生**、不单独设置(可读性标识形如 `com.artislong.GreetingService#sayHello(java.lang.String)`,运行时默认用其 `p_` 紧凑 hash 串做 key),无需你手动维护标识;插件方法命名请遵循下文「插件方法命名建议」 - 融合通过 `SandboxInvokerHolder` + SPI 自动注册 `SandboxFactory` 实现,api **不静态依赖** sandbox 包 - **初始化引导**:当 classpath 中存在 sandbox 模块时,其 `SandboxInvokerAdapter` 会经 SPI 被 `SandboxInvokerHolder` 自动发现并注册;如需加载外部配置(开启默认沙箱 / 每方法独立策略),调用 `SandboxFusionBootstrap.init(SandboxConfigSource)` 即可(Spring 场景由 `dynamic-plugin-spring-boot-starter` 自动完成)。`enabled` 默认 `false`:即“关闭后无默认沙箱、需手动创建”;置 `true` 后默认 basic 生效 - **手动优先于默认**:若你已用 `SandboxManager.createSandboxForMethod(...)`(或旧式 `createSandbox`)为某契约方法显式建了沙箱,api 调用该方法时会**复用**该权威沙箱,不会再自动创建默认沙箱;手动 `sandbox.execute(...)` 包裹内部也不会被 api 二次包裹(内置嵌套防护 `SandboxNestingGuard`) **使用场景** - 对第三方 / 不可信插件做隔离执行、限流与资源封顶 - 多租户环境下防止插件越权访问宿主类路径或系统资源 ### dynamic-plugin-sample `dynamic-plugin-sample` 是示例模块,提供可运行的演示: - `dynamic-plugin-sample-admin`:管理端示例 - `dynamic-plugin-sample-app`:业务应用示例 - `dynamic-plugin-sample-plugin`:插件实现示例 - `dynamic-plugin-sample-api`:API / 契约使用示例 - `dynamic-plugin-sample-sandbox`:沙箱使用示例 #### 真实端到端用例(jdk17 沙箱实际执行插件方法) 示例位于 `dynamic-plugin-sample-sandbox/src/main/java/com/artislong/sandboxdemo/`: - `GreetingService.java`:契约接口(契约类 + 方法 = 插件调用单元) - `GreetingServiceImpl.java`:插件实现;内部通过 `SandboxNestingGuard.isActive()` 探测自己是否处于沙箱执行上下文 - `SandboxFusionDemo.java`:端到端主类,覆盖四个真实场景并直接运行验证 > 调用入口始终写 `plugin.getProxy().sayHello("world")`;走沙箱还是直跑、用哪一档,由依赖与配置决定。**下面是实际跑出来的输出**(非示意): ```text ======== 环境准备:注册插件实现实例并启用实例缓存 ======== 插件方法可读标识: com.artislong.sandboxdemo.GreetingService#sayHello(java.lang.String) ======== 场景一:仅引入 api(融合未激活)→ 当前环境直跑,不进沙箱 ======== invoker 已注入: false 融合激活: false 返回: Hello, world | executed-in-sandbox=false 断言(executed-in-sandbox=false): true ======== 场景二:api + sandbox 开启融合 → 调用自动走默认 jdk17 沙箱(按方法签名隔离) ======== invoker 已注入: true 融合对 sayHello 激活: true [INFO] [p_6b5d82c0] JDK 17+ sandbox initialized for plugin: p_6b5d82c0 返回: Hello, world | executed-in-sandbox=true 断言(executed-in-sandbox=true): true 自动沙箱已注册 registry: true ======== 场景三:开启融合 + 手动建权威沙箱 → 默认沙箱让位(手动优先) ======== [INFO] [p_6b5d82c0] JDK 17+ sandbox initialized for plugin: p_6b5d82c0 手动沙箱档位: ENHANCED registry 中该 key 是否存在: true 返回: Hello, world | executed-in-sandbox=true 断言(复用手动沙箱, executed-in-sandbox=true): true ======== 场景四:用户手动 sandbox.execute 包裹 → 内部 api 不再二次包裹(嵌套防护) ======== [INFO] [p_6b5d82c0] JDK 17+ sandbox initialized for plugin: p_6b5d82c0 返回: Hello, world | executed-in-sandbox=true 断言(外层沙箱执行, executed-in-sandbox=true 且无二次包裹异常): true === 调试:当前环境已解析的方法标识(可读串 -> 运行时 key)=== pluginId mapping for com.artislong.sandboxdemo.GreetingService: com.artislong.sandboxdemo.GreetingService#sayHello(java.lang.String) -> p_6b5d82c0 ``` 四个场景说明: 1. **仅 api / 融合未激活** → `invoker == null`,方法在当前环境(无沙箱)直接执行,`executed-in-sandbox=false`。 2. **开启融合** → `SandboxFusionBootstrap.init(...)` 通过 SPI 自动发现 `SandboxInvokerAdapter` 并注入;调用经 `Plugin.handleMethodCall` 自动派生方法标识 `p_6b5d82c0`,首次触发 `JDK 17+ sandbox initialized`,实际在 **Jdk17BasicSandbox** 内执行,`executed-in-sandbox=true`,沙箱自动注册到 `SandboxRegistry`。 3. **手动建权威沙箱** → 用 `SandboxManager.createSandboxForMethod(...)`(内部 `PluginIdUtil.resolve` 得到同一个 `p_6b5d82c0`)建 ENHANCED 沙箱;后续 api 调用命中 registry 复用,**默认沙箱不再创建**(手动优先)。 4. **手动 `sandbox.execute(...)` 包裹** → 外层已进入沙箱(`SandboxNestingGuard` 激活),内部 api 的 `isFusionActive()` 返回 false,直接执行,绝不二次包裹(嵌套防护)。 运行方式(已编译前提下;仓库未打包 assembled 依赖,按 `SandboxFusionDemo` 顶部注释组装 classpath 即可,IDE 内 Run main 亦可): ```bash # 1) 构建融合相关模块(融合类已在 target/classes;如未编译请先构建) mvn -q -pl dynamic-plugin-api,dynamic-plugin-sandbox,dynamic-plugin-sandbox-jdk17 install -DskipTests # 2) 运行示例主类:classpath = api + sandbox + sandbox-jdk17 + hutool + cglib + asm + slf4j-api + slf4j-simple + demo-classes java -cp "<上述路径>;..." com.artislong.sandboxdemo.SandboxFusionDemo ``` > 关键点:**插件方法标识自动派生、融合默认关闭且关闭后直跑、手动沙箱优先于默认、嵌套不重复包裹**,在该端到端示例中逐一为真。 ## 模块结构 ``` dynamic-plugin ├── dynamic-plugin-admin # 管理端核心:上传/暂存/触发加载 ├── dynamic-plugin-admin-framework # 管理端框架集成抽象层 │ └── dynamic-plugin-admin-spring # 管理端 Spring Boot 适配 ├── dynamic-plugin-core # 核心模块:加载/卸载/生命周期/集群同步 ├── dynamic-plugin-api # 插件 API:契约包装与运行时调用入口 ├── dynamic-plugin-api-framework # API 框架集成层 │ ├── dynamic-plugin-api-framework-java # 无框架时的实例生命周期管理 │ └── dynamic-plugin-api-framework-spring # Spring 容器加载 Bean 为插件实例 ├── dynamic-plugin-datasource # 插件数据源适配层 │ ├── dynamic-plugin-datasource-oss # OSS 数据源 │ └── dynamic-plugin-datasource-sftp # SFTP 数据源 ├── dynamic-plugin-framework # 框架集成层 │ ├── dynamic-plugin-framework-spring # Spring 集成 │ ├── dynamic-plugin-framework-xxljob # XXL-Job 集成 │ ├── dynamic-plugin-framework-mybatisplus # MyBatis-Plus 集成 │ ├── dynamic-plugin-framework-elasticjob # ElasticJob 集成 │ └── dynamic-plugin-framework-rocketmq # RocketMQ 集成 ├── dynamic-plugin-sandbox # 内存沙箱基座(接口/策略/抽象) │ ├── dynamic-plugin-sandbox-jdk17 # JDK17 沙箱实现(默认) │ └── dynamic-plugin-sandbox-jdk8 # JDK8 沙箱实现 └── dynamic-plugin-sample # 示例项目 ├── dynamic-plugin-sample-admin # 管理端示例 ├── dynamic-plugin-sample-app # 业务应用示例 ├── dynamic-plugin-sample-plugin # 插件示例 ├── dynamic-plugin-sample-api # API/契约示例 └── dynamic-plugin-sample-sandbox # 沙箱示例 ``` > 坐标均为 `com.artislong:模块名`,版本由根 POM 的 `${revision}-${changelist}` 统一控制(默认 `1.0.0-SNAPSHOT`,可通过 `dev/test/prod` profile 切换 `changelist`)。 ## 快速开始 ### 0. 约定:契约分离 将需要被插件化扩展的 **接口 / 抽象类 / 普通类** 放到一个独立 Maven 模块(契约包 `plugin-contract.jar`),业务系统与插件实现方都依赖它。 ```java // 契约包中定义(对外发布给插件实现方) public interface GreetingService { String sayHello(String name); } ``` ### 1. 编写插件实现 插件实现方依赖契约包,编写实现类并打成 JAR: ```java // 插件模块中实现 public class GreetingServiceImpl implements GreetingService { @Override public String sayHello(String name) { return "Hello, " + name + " from dynamic plugin!"; } } ``` ### 2. 业务系统引入依赖 ```xml com.artislong dynamic-plugin-core ${dynamic-plugin.version} com.artislong dynamic-plugin-api ${dynamic-plugin.version} com.artislong dynamic-plugin-datasource-sftp ${dynamic-plugin.version} com.artislong dynamic-plugin-sandbox ${dynamic-plugin.version} com.artislong dynamic-plugin-sandbox-jdk17 ${dynamic-plugin.version} ``` ### 3. 配置存储与沙箱 ```yaml dynamic: plugin: storage: type: local # local / oss / sftp 三选一 local: path: /path/to/plugins # 可选:内存沙箱(仅当引入 sandbox 时生效) sandbox: enabled: true # 默认 false:不引 sandbox 或关闭时插件直接执行 level: BASIC # BASIC / ENHANCED / STRICT max-memory: 20971520 # 单插件内存上限(字节),可空沿用档位默认 allow-classes: "com.artislong.plugin.*" # 类路径白名单(逗号/正则) ``` ### 4. 部署管理平台(可选,分布式场景) 部署 `dynamic-plugin-admin`(配合 `dynamic-plugin-admin-spring`),统一上传插件并触发加载。单体场景可直接内嵌 admin。 ```yaml dynamic: plugin: admin: access-token: your-access-token storage: type: local local: path: /path/to/plugins ``` ### 5. 上传并加载插件 - 远程:通过管理平台上传插件 JAR 包,点击「加载」由 admin 暂存到存储后端并触发 core 加载 - 本地:将插件 JAR 放到配置的 `local.path`,core 直接加载(跳过暂存与下载) ### 6. 通过 API 调用插件实现 ```java // 按契约获取插件代理并调用(是否走沙箱由配置/注解决定) Plugin plugin = PluginManager.getPlugin(GreetingService.class, router); String result = plugin.getProxy().sayHello("world"); ``` 若开启了 sandbox 融合(步骤 3),以上调用将**自动在沙箱内隔离执行**;未引入或未开启时则直接在当前环境执行。 ## 集群部署 在集群环境中,Dynamic Plugin 通过集群同步器确保多节点的插件状态一致(管理端为状态中心,各执行器 core 拉齐): ``` +------------------+ +------------------+ | | | | | 管理端平台 +<---->+ 执行器节点1 | | (Admin) | | (Executor/core) | | | | | +------------------+ +------------------+ | | +------------------+ | | | +---------------->+ 执行器节点2 | | | (Executor/core) | | | | | +------------------+ | | +------------------+ | | | +---------------->+ 执行器节点3 | | (Executor/core) | | | +------------------+ ``` - 分布式:`admin` 独立部署 + `core` 集群 + 共享存储(OSS/SFTP) - 单体:`admin` 内嵌 + `core` 内嵌 + 可用 Local 存储 ## 扩展点 ### 插件处理器扩展 通过实现 `DynamicPluginProcessor` 接口,可以扩展插件的处理逻辑: ```java public class CustomPluginProcessor implements DynamicPluginProcessor { @Override public void doLoad(DynamicPluginContext context) { // 自定义加载逻辑 } @Override public void doUnload(DynamicPluginContext context) { // 自定义卸载逻辑 } } ``` ### 插件数据源扩展 通过实现 `PluginDataSource` 抽象类,可以扩展插件的数据源: ```java public class CustomPluginDataSource extends PluginDataSource { public CustomPluginDataSource(PluginStore pluginStore, String filePath) { super(pluginStore, filePath); } @Override public InputStream getInputStream() throws Exception { // 自定义获取输入流的逻辑 } } ``` ### 沙箱策略扩展(可选) 引入 sandbox 后,可通过 `SandboxPolicy` / `SandboxSettings` 配置或代码注入,调整沙箱档位(basic/enhanced/strict)与资源限制(内存 / 线程 / CPU / 类路径白名单 / 文件 / 网络)。 ## 技术栈与设计说明 - **JDK**:17+(当前 jdk17 分支;另有 jdk8 分支与对应的 sandbox-jdk8 实现) - **Spring Boot**:3.5.11(SB4 通过 BOM profile 预留) - **中间件**:MyBatis-Plus 3.5.17、XXL-Job 2.5.0、ElasticJob 3.0.4、RocketMQ 2.3.4、Hutool 5.8.42 - **字节码 / 工具**:Javassist、CFR 反编译器、java-diff-utils、zip4j、CGLIB - **多版本兼容架构**:核心模块(core / api / datasource / admin / framework)向「框架中立共享层」收敛(`--release 8` 字节码,零 `javax/jakarta/spring` 约束);仅 Spring 适配层按 Spring Boot 版本分叉(sb27 / sb3),沙箱按 JDK 分叉(jdk8 / jdk17)。详见 `docs/jdk17-multiversion-arch-design.md` - **沙箱融合设计**:api 与 sandbox 深度融合、配置驱动、按需引入的落地方案见 `docs/sandbox-api-fusion-design.md` ## 贡献指南 欢迎贡献代码、报告问题或提出改进建议。请遵循以下步骤: 1. Fork 项目 2. 创建特性分支 (`git checkout -b feature/amazing-feature`) 3. 提交更改 (`git commit -m 'Add some amazing feature'`) 4. 推送到分支 (`git push origin feature/amazing-feature`) 5. 创建 Pull Request ## 许可证 本项目基于 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 开源。