插件系统
LCGYL Framework 的插件系统是其核心特性之一,提供灵活的扩展机制和模块化架构。
插件概述
什么是插件?
插件是一个独立的功能模块,可以在运行时动态加载、卸载和更新,无需重启应用。
核心特性
- 模块化:功能独立,互不干扰
- 热加载:运行时动态加载/卸载
- 依赖管理:自动解析插件依赖
- 版本控制:支持版本兼容性检查
- 隔离性:独立的类加载器
创建插件
插件结构
my-plugin/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/plugin/
│ │ ├── MyPlugin.java
│ │ ├── MyService.java
│ │ └── MyExtension.java
│ └── resources/
│ └── plugin.yaml
├── build.gradle
└── README.md定义插件类
java
package com.example.plugin;
import com.lcgyl.framework.core.plugin.Plugin;
import com.lcgyl.framework.core.plugin.PluginContext;
public class MyPlugin implements Plugin {
private static final Logger logger = LoggerFactory.getLogger(MyPlugin.class);
@Override
public String getId() {
return "my-plugin";
}
@Override
public String getName() {
return "我的插件";
}
@Override
public String getVersion() {
return "1.0.0";
}
@Override
public void onLoad(PluginContext context) {
logger.info("插件加载中...");
// 初始化资源
}
@Override
public void onEnable(PluginContext context) {
logger.info("插件启用中...");
// 注册服务、监听器等
context.registerService(MyService.class, new MyServiceImpl());
}
@Override
public void onDisable(PluginContext context) {
logger.info("插件禁用中...");
// 清理资源
}
@Override
public void onUnload(PluginContext context) {
logger.info("插件卸载中...");
// 释放所有资源
}
}插件描述文件
plugin.yaml:
yaml
id: my-plugin
name: 我的插件
version: 1.0.0
description: 这是一个示例插件
author: 张三
website: https://example.com
# 主类
main: com.example.plugin.MyPlugin
# 依赖
dependencies:
- id: core-plugin
version: ">=1.0.0"
- id: optional-plugin
version: ">=2.0.0"
optional: true
# 框架版本要求
framework:
minVersion: "1.0.0"
maxVersion: "2.0.0"
# 权限
permissions:
- database.read
- database.write
- network.httpGradle 配置
gradle
plugins {
id 'java'
}
group = 'com.example'
version = '1.0.0'
dependencies {
compileOnly 'com.lcgyl:lcgyl-framework-core:1.0.0'
}
jar {
manifest {
attributes(
'Plugin-Id': 'my-plugin',
'Plugin-Version': version,
'Plugin-Class': 'com.example.plugin.MyPlugin'
)
}
}插件生命周期
生命周期阶段
加载 → 初始化 → 启用 → 运行 → 禁用 → 卸载生命周期回调
java
public class MyPlugin implements Plugin {
@Override
public void onLoad(PluginContext context) {
// 1. 加载阶段
// - 加载配置文件
// - 初始化日志
// - 准备资源
}
@Override
public void onEnable(PluginContext context) {
// 2. 启用阶段
// - 注册服务
// - 注册监听器
// - 注册命令
// - 启动任务
}
@Override
public void onDisable(PluginContext context) {
// 3. 禁用阶段
// - 停止任务
// - 注销监听器
// - 保存数据
}
@Override
public void onUnload(PluginContext context) {
// 4. 卸载阶段
// - 释放资源
// - 关闭连接
// - 清理缓存
}
}插件上下文
PluginContext API
java
public class MyPlugin implements Plugin {
@Override
public void onEnable(PluginContext context) {
// 获取配置
Configuration config = context.getConfiguration();
String dbUrl = config.getString("database.url");
// 注册服务
context.registerService(MyService.class, new MyServiceImpl());
// 获取服务
DatabaseService dbService = context.getService(DatabaseService.class);
// 注册监听器
context.registerListener(new MyEventListener());
// 获取数据目录
Path dataDir = context.getDataDirectory();
// 获取日志
Logger logger = context.getLogger();
// 调度任务
context.scheduleTask(() -> {
// 定时任务
}, 0, 60, TimeUnit.SECONDS);
}
}插件配置
config.yaml:
yaml
database:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: secret
cache:
enabled: true
ttl: 3600
features:
feature1: true
feature2: false读取配置:
java
@Override
public void onEnable(PluginContext context) {
Configuration config = context.getConfiguration();
// 读取配置
String dbUrl = config.getString("database.url");
int cacheTtl = config.getInt("cache.ttl", 3600);
boolean feature1 = config.getBoolean("features.feature1", false);
// 读取嵌套配置
Configuration dbConfig = config.getSection("database");
String username = dbConfig.getString("username");
}扩展点
定义扩展点
java
@ExtensionPoint
public interface DataProcessor {
String getName();
boolean supports(String dataType);
Object process(Object data);
}实现扩展
java
@Extension
public class JsonDataProcessor implements DataProcessor {
@Override
public String getName() {
return "JSON Processor";
}
@Override
public boolean supports(String dataType) {
return "json".equals(dataType);
}
@Override
public Object process(Object data) {
// 处理 JSON 数据
return JsonParser.parse(data.toString());
}
}
@Extension
public class XmlDataProcessor implements DataProcessor {
@Override
public String getName() {
return "XML Processor";
}
@Override
public boolean supports(String dataType) {
return "xml".equals(dataType);
}
@Override
public Object process(Object data) {
// 处理 XML 数据
return XmlParser.parse(data.toString());
}
}使用扩展
java
@Component
public class DataService {
@Inject
private ExtensionRegistry extensionRegistry;
public Object processData(String dataType, Object data) {
// 获取所有扩展
List<DataProcessor> processors = extensionRegistry.getExtensions(DataProcessor.class);
// 找到支持的处理器
for (DataProcessor processor : processors) {
if (processor.supports(dataType)) {
return processor.process(data);
}
}
throw new UnsupportedDataTypeException(dataType);
}
}扩展优先级
java
@Extension(priority = 100) // 高优先级
public class PrimaryProcessor implements DataProcessor {
}
@Extension(priority = 50) // 中优先级
public class SecondaryProcessor implements DataProcessor {
}
@Extension(priority = 1) // 低优先级
public class FallbackProcessor implements DataProcessor {
}插件依赖
声明依赖
yaml
# plugin.yaml
dependencies:
# 必需依赖
- id: database-plugin
version: ">=1.0.0"
# 可选依赖
- id: cache-plugin
version: ">=1.0.0"
optional: true
# 版本范围
- id: security-plugin
version: ">=1.0.0,<2.0.0"使用依赖
java
@Override
public void onEnable(PluginContext context) {
// 获取依赖插件的服务
DatabaseService dbService = context.getService(DatabaseService.class);
// 检查可选依赖
if (context.hasPlugin("cache-plugin")) {
CacheService cacheService = context.getService(CacheService.class);
// 使用缓存
}
}依赖解析顺序
1. 解析所有插件的依赖
2. 构建依赖图
3. 检测循环依赖
4. 按拓扑排序加载插件热加载
启用热加载
java
PluginManager pluginManager = context.getPluginManager();
// 加载插件
pluginManager.loadPlugin(new File("plugins/my-plugin.jar"));
// 启用插件
pluginManager.enablePlugin("my-plugin");
// 禁用插件
pluginManager.disablePlugin("my-plugin");
// 卸载插件
pluginManager.unloadPlugin("my-plugin");
// 重新加载插件
pluginManager.reloadPlugin("my-plugin");监听插件事件
java
@Component
public class PluginEventListener {
@Subscribe
public void onPluginLoaded(PluginLoadedEvent event) {
logger.info("插件已加载: {}", event.getPlugin().getName());
}
@Subscribe
public void onPluginEnabled(PluginEnabledEvent event) {
logger.info("插件已启用: {}", event.getPlugin().getName());
}
@Subscribe
public void onPluginDisabled(PluginDisabledEvent event) {
logger.info("插件已禁用: {}", event.getPlugin().getName());
}
@Subscribe
public void onPluginUnloaded(PluginUnloadedEvent event) {
logger.info("插件已卸载: {}", event.getPluginId());
}
}插件隔离
类加载器隔离
每个插件有独立的类加载器,确保插件之间的类不会冲突。
java
// 插件 A 使用 Gson 2.8
// 插件 B 使用 Gson 2.10
// 两者互不影响资源隔离
java
@Override
public void onEnable(PluginContext context) {
// 获取插件专属的数据目录
Path dataDir = context.getDataDirectory();
// 返回: plugins/my-plugin/data/
// 获取插件专属的配置目录
Path configDir = context.getConfigDirectory();
// 返回: plugins/my-plugin/config/
// 获取插件专属的日志
Logger logger = context.getLogger();
// 日志前缀: [my-plugin]
}插件通信
服务注册与发现
java
// 插件 A:注册服务
@Override
public void onEnable(PluginContext context) {
context.registerService(PaymentService.class, new PaymentServiceImpl());
}
// 插件 B:使用服务
@Override
public void onEnable(PluginContext context) {
PaymentService paymentService = context.getService(PaymentService.class);
paymentService.processPayment(order);
}事件通信
java
// 插件 A:发布事件
@Override
public void onEnable(PluginContext context) {
EventBus eventBus = context.getEventBus();
eventBus.publish(new OrderCreatedEvent(order));
}
// 插件 B:订阅事件
@Component
public class OrderEventListener {
@Subscribe
public void onOrderCreated(OrderCreatedEvent event) {
// 处理订单创建事件
}
}官方插件
数据库插件
yaml
dependencies:
- id: lcgyl-database-plugin
version: ">=1.0.0"java
@Inject
private DatabaseService databaseService;
public void saveUser(User user) {
databaseService.execute("INSERT INTO users ...", user);
}缓存插件
yaml
dependencies:
- id: lcgyl-cache-plugin
version: ">=1.0.0"java
@Inject
private CacheService cacheService;
public User getUser(Long id) {
return cacheService.get("user:" + id, () -> userRepository.findById(id));
}Web 插件
yaml
dependencies:
- id: lcgyl-web-plugin
version: ">=1.0.0"java
@Controller
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}最佳实践
1. 保持插件独立
java
// ✅ 推荐:插件自包含
public class MyPlugin implements Plugin {
// 所有功能在插件内部实现
}
// ❌ 不推荐:依赖太多其他插件
dependencies:
- plugin-a
- plugin-b
- plugin-c
- plugin-d2. 正确处理生命周期
java
// ✅ 推荐:在正确的阶段执行操作
@Override
public void onEnable(PluginContext context) {
// 注册服务
}
@Override
public void onDisable(PluginContext context) {
// 清理资源
}3. 使用配置而不是硬编码
java
// ✅ 推荐:使用配置
String apiUrl = config.getString("api.url");
// ❌ 不推荐:硬编码
String apiUrl = "https://api.example.com";4. 提供默认配置
java
@Override
public void onLoad(PluginContext context) {
// 保存默认配置
context.saveDefaultConfig();
}5. 记录日志
java
@Override
public void onEnable(PluginContext context) {
Logger logger = context.getLogger();
logger.info("插件启用成功");
logger.debug("配置: {}", config);
}常见问题
Q: 插件加载失败怎么办?
A: 检查以下几点:
plugin.yaml格式是否正确- 主类路径是否正确
- 依赖是否满足
- 框架版本是否兼容
Q: 如何调试插件?
A:
properties
logging.level.com.lcgyl.framework.core.plugin=DEBUGQ: 插件之间如何共享数据?
A: 使用服务注册或事件总线。
java
// 服务方式
context.registerService(SharedService.class, impl);
// 事件方式
eventBus.publish(new DataSharedEvent(data));