DataCap 新插件系统特性详解:为未来而生的插件管理

44 阅读4分钟

DataCap 新插件系统支持多种加载模式

  • Properties 配置文件方式
  • 编译后的 POM 方式
  • 目录方式
  • POM 方式
  • SPI 方式

新插件系统的特性

  • 使用 WeakHashMap 避免内存泄漏
  • 支持每个插件独立的类加载器
  • 提供上下文切换机制
  • 支持 SPI 方式加载服务
  • 支持通过注解 @InjectService 方式加载服务
  • 服务实现的自动发现
  • 基于 Guice 的依赖注入
  • 支持命名绑定
  • 支持单例绑定
  • 支持多实现服务绑定
  • 插件的安装
  • 插件的启动停止
  • 插件的卸载(支持常规卸载和强制卸载)
  • 插件的备份
  • 支持自动扫描加载
  • 支持定时重载
  • 支持卸载时自动备份
  • 支持从多个源获取版本信息
  • 版本信息缓存
  • 版本冲突时的备份机制
  • 插件基本信息管理
  • 插件状态跟踪
  • 加载时间记录

更多特性我们将详细讲解我们的新插件系统

我们已实现一个 datacapp-plugin-local 的插件为示例来讲解。

构建一个新的插件需要添加 datacap-plugin 依赖,将以下代码添加到 pom.xml 文件中。

<dependency>
    <groupId>io.edurt.datacap</groupId>
    <artifactId>datacap-plugin</artifactId>
    <version>${VERSION}</version>
</dependency>

${VERSION} 是指发布到中央仓库中的版本,可以到 Maven 中央仓库中查找最新版本。

构建 LocalPlugin

我们先来新建一个 LocalPlugin 插件,源码如下:

package io.edurt.datacap.test;

import com.google.inject.name.Names;
import io.edurt.datacap.plugin.Plugin;

public class LocalPlugin
        extends Plugin
{
}

插件系统会自动扫描配置和版本等。

如果需要自定义插件的配置可以增加以下代码:


@Override
protected void configurePlug()
{
    bind(String.class).annotatedWith(Names.named("host")).toInstance("localhost");
}

构建插件要用的 Service 服务

新的插件系统支持多个实现服务,如果用户需要多个服务的话可参考如下,比如我构建了 DataService 用于任务的输出。基于他实现了以下自定义功能

  • ConsoleService
  • LogService
  • AnnotationService

数据服务 DataService 源码如下

package io.edurt.datacap.test;

import io.edurt.datacap.plugin.Service;

public interface DataService
        extends Service
{
    void print();
}

ConsoleService 源码如下

package io.edurt.datacap.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ConsoleService
        implements DataService
{
    @Override
    public void print()
    {
        log.info("Console Service");
    }
}

LogService 源码如下

package io.edurt.datacap.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogService
        implements DataService
{
    @Override
    public void print()
    {
        log.info("Log Service");
    }
}

AnnotationService 源码如下

package io.edurt.datacap.test;

import io.edurt.datacap.plugin.service.InjectService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@InjectService
public class AnnotationService
        implements DataService
{
    @Override
    public void print()
    {
        log.info("Annotation Service");
    }
}

通过 @InjectService 注解进行服务的扫描配置,默认使用插件类所在包

需要注意的是在 DataService 中添加了 extends io.edurt.datacap.plugin.Service 只有实现它才能触发系统的动态加载 Service 功能。

添加插件扫描器

resources 目录下依次添加 META-INFservices,添加后在 services 目录下添加两个配置文件:

新建 io.edurt.datacap.plugin.Plugin 文件,内容如下

io.edurt.datacap.test.LocalPlugin

新建 io.edurt.datacap.plugin.Service 文件,内容如下

io.edurt.datacap.test.ConsoleService
io.edurt.datacap.test.LogService

将我们创建的服务添加到该文件内。

实例化插件系统

对于插件的操作都是基于 PluginManager 来实现。

实例化 PluginManager,代码如下

Path projectRoot = PluginPathUtils.findProjectRoot();
PluginConfig config = PluginConfig.builder()
        .pluginsDir(projectRoot.resolve("test/datacap-test-plugin"))
        .build();

PluginManager pluginManager = new PluginManager(config);
pluginManager.start();

这里的 pluginsDir 配置要指定加载插件的目录,如果是相对路径系统会自动根据当前路径自动扫描。

如果当前目录在 /root/datacap,配置的 pluginsDir 目录为 plugins 则系统扫描的路径为 /root/datacap/plugins。系统会递归扫描扫描所有的目录进行加载插件。

获取插件

实例化插件后我们得到 PluginManager 的实例,如果我们要获取插件系统中的 Local 插件,我们可以使用以下方式获取

pluginManager.getPlugin("Local")

系统返回 Optional<Plugin> 类型。

注意系统加载时会自动移除 Plugin 。规则为:

  • LocalPlugin 加载后 Local
  • PluginLocal 加载后 Local

获取 Service

我们在系统中增加了 ConsoleServiceLogService 服务,如果我们要获取某一个可以通过以下方式实现

DataService service = value.getService(ConsoleService.class);

这样就能获取到 ConsoleService 服务。

我们也可以通过服务名字获取,代码如下

DataService logService = value.getService(DataService.class, "LogService");

指定的服务别名为构建的类名,比如我们的类名为 class AAA {} 那么这个名称为 AAA

我们还可以获取所有的服务列表,代码如下

Set<DataService> services = value.getAllServices(DataService.class);

这样我们就能获取到加载到该插件内的所有 Service。

这里的 value 是指我们获取到的插件。