Skip to content

插件系统

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

Gradle 配置

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

2. 正确处理生命周期

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: 检查以下几点:

  1. plugin.yaml 格式是否正确
  2. 主类路径是否正确
  3. 依赖是否满足
  4. 框架版本是否兼容

Q: 如何调试插件?

A:

properties
logging.level.com.lcgyl.framework.core.plugin=DEBUG

Q: 插件之间如何共享数据?

A: 使用服务注册或事件总线。

java
// 服务方式
context.registerService(SharedService.class, impl);

// 事件方式
eventBus.publish(new DataSharedEvent(data));

下一步

Released under the Apache License 2.0