插件(Plugin)
插件模块采用独立的类加载器(ClassLoader)来管理私有包、类、资源文件等,设计目标是在接口开发模式下,将需求进行更细颗粒度拆分,从而达到一个理想化可重用代码的封装形态。
每个插件都是封闭的世界,插件与外界之间沟通的唯一方法是通过业务接口调用,管理这些插件的容器被称之为插件工厂(IPluginFactory),其负责插件的分析、加载和初始化,以及插件的生命周期管理,插件模块支持创建多个插件工厂实例,工厂对象之间完全独立,无任何依赖关系。
Maven包依赖
<dependency>
<groupId>net.ymate.platform</groupId>
<artifactId>ymate-platform-plugin</artifactId>
<version>2.1.4</version>
</dependency>
插件模块的整体结构
IApplication (YMP 容器)
└── Plugins (模块, IPlugins) ── Plugins.get()
└── IPluginFactory (插件工厂)
├── IPluginConfig 配置
├── IBeanLoader 插件专属对象加载器
├── IPluginBeanFactory 插件Bean容器(父容器 = 宿主 BeanFactory)
│ └── PluginMeta ... 插件元数据注册表
└── PluginClassLoader ... 每个外部插件目录一个独立类加载器
└── IPlugin 实例 ← @Plugin 声明的插件启动类(同时实现业务接口)
相关接口职责说明:
| 接口/类 | 说明 |
|---|---|
IPlugins / Plugins | 插件模块(YMP Module),Plugins.get() 返回默认模块实例,持有默认插件工厂 |
IPluginFactory | 插件工厂:负责插件的加载、注册、启动、停止、卸载与重载 |
IPluginConfig | 插件模块配置,定义了配置项的常量名称 |
IPluginBeanFactory | 插件 Bean 容器,以宿主 BeanFactory 作为父容器,注册表存放 PluginMeta |
IPlugin | 插件实例契约(初始化、启动、停止、销毁) |
AbstractPlugin | IPlugin 的抽象实现,自定义插件建议直接继承 |
IPluginContext | 插件环境上下文,可获取所属容器、工厂与自身元数据 |
PluginMeta | 插件元数据描述对象(由 @Plugin 注解解析而来) |
PluginClassLoader | 插件类加载器,承载插件私有的 JAR 包与类文件 |
默认插件工厂
插件工厂分为两种,一种是以模块的形式封装,由框架初始化时根据配置参数自动构建,称之为默认插件工厂(有且仅能存在一个默认工厂实例),另一种是通过代码手动配置构建的自定义插件工厂,不同之处在于默认插件工厂与框架结合得更紧密,两种模式可以并存。
默认插件工厂的初始化时序如下:
Plugins.onBeforeInit:读取模块配置(properties / 编程式),结合主类上的@PluginConf注解,构建默认插件工厂Plugins.doLoadConfig:将插件配置对象挂载到模块Plugins.onInit:注册PluginEvent事件,并监听框架的APPLICATION_INITIALIZED事件,随后调用pluginFactory.initialize(owner)完成插件的加载与注册- 容器初始化完成后触发
APPLICATION_INITIALIZED,此时调用pluginFactory.startup()逐个初始化并启动插件 Plugins.onClose:调用pluginFactory.close(),逐个停止并销毁插件
注意:插件是在容器初始化完成之后才被启动的,因此不要在插件的
doInitialize中假定其它业务模块已经完全就绪。
配置文件参数说明
#-------------------------------------
# Plugin插件模块初始化参数
#-------------------------------------
# 是否启用插件模块(若禁用则在框架初始化不会创建默认插件工厂对象,不影响自定义插件工厂的使用),默认为true
ymp.configs.plugin.enabled=
# 插件主目录路径,可选参数,默认值为${root}/plugins(路径不存在时将自动创建)
ymp.configs.plugin.plugin_home=
# 插件自动扫描的包名前缀集合(若未设置将默认包含主程序类所在包),多个包名之间用'|'分隔
ymp.configs.plugin.package_names=
# 插件自动扫描时排除包名称集合,多个包名之间用'|'分隔, 被包含在包路径下的类文件在扫描过程中将被忽略
ymp.configs.plugin.excluded_package_names=
# 插件自动扫描时排除包文件名称集合,多个包名之间用'|'分隔, 被包含的JAR或ZIP文件在扫描过程中将被忽略
ymp.configs.plugin.excluded_file_names=
# 是否允许插件自动启动,默认为true
ymp.configs.plugin.automatic=
# 是否加载当前CLASSPATH内的所有包含插件配置文件的JAR包,默认为false
ymp.configs.plugin.included_classpath=
# 设置指定插件插件状态为禁用(xxx代表插件唯一标识或插件类名称,其值必须是disabled)
ymp.params.plugin.xxx=disabled
编程式配置(DefaultPluginConfigurable)
除配置文件外,也可以通过 DefaultPluginConfigurable 以编码方式向传入:
IModuleConfigurer moduleConfigurer = DefaultPluginConfigurable.builder()
.enabled(true)
.pluginHome("${root}/plugins")
.packageNames("com.company|cn.company")
.automatic(true)
.includedClasspath(false)
.build();
说明:
DefaultPluginConfigurable.Builder提供enabled/pluginHome/packageNames/excludedPackageNames/excludedFileNames/automatic/includedClasspath七个配置项,与配置文件参数及@PluginConf注解一一对应(excludedPackageNames与excludedFileNames于2.1.4起提供)。
配置注解参数说明
@PluginConf
配置注解声明在主程序类(即 IApplication.SYSTEM_MAIN_CLASS 指定的类)上,其作用是作为上述配置文件参数的兜底默认值——当配置文件中未读取到对应配置项时,才会采用注解值。
| 配置项 | 描述 |
|---|---|
| enabled | 是否启用插件模块,默认为 true |
| pluginHome | 插件主目录路径,可选参数,默认值为 ${root}/plugins |
| packageNames | 插件自动扫描的包名前缀集合(若未设置将默认包含主程序类所在包),可选参数 |
| excludedPackageNames | 插件自动扫描时排除包名称集合,可选参数 |
| excludedFileNames | 插件自动扫描时排除包文件名称集合,可选参数 |
| includeClasspath | 是否扫描当前 CLASSPATH 内的相关插件,默认为 false |
| automatic | 是否允许插件自动启动,默认为 true |
示例代码:
@EnableAutoScan
@PluginConf(enabled = true,
pluginHome = "${root}/plugins",
packageNames = {"com.company", "cn.company"},
automatic = true)
public class Starter {
static {
System.setProperty(IApplication.SYSTEM_MAIN_CLASS, Starter.class.getName());
}
public static void main(String[] args) throws Exception {
try (IApplication application = YMP.run(args)) {
// ...
}
}
}
默认插件工厂事件
默认插件工厂是通过框架的事件服务订阅进行处理,包括以下事件类型:
| 事务类型 | 说明 |
|---|---|
| PLUGIN_INITIALIZED | 插件初始化事件 |
| PLUGIN_STARTED | 插件启动事件 |
| PLUGIN_SHUTDOWN | 插件停止事件 |
| PLUGIN_DESTROYED | 插件销毁事件 |
监听示例:
Events events = application.getEvents();
events.registerListener(Events.MODE.NORMAL, PluginEvent.class, (IEventListener<PluginEvent>) context -> {
// 事件源对象即为对应的插件实例
IPlugin plugin = context.getOwner();
System.out.println(context.getEventName() + " -> " + plugin.getPluginContext().getPluginMeta().getId());
return false;
});
注意:上述事件仅由默认插件工厂( 即插件模块)触发。这是因为事件的投递是由插件模块在创建默认工厂时安装的一层
IPluginEventListener桥接器完成的,通过代码手工创建的自定义插件工厂不会触发该事件(详见"自定义插件工厂事件处理"一节)。
自定义插件工厂
创建自定义插件工厂时,需要对该插件工厂进行一系列配置,框架针对插件工厂的配置提供了注解和手工编码两种方式,可以根据实际情况自由选择,在通常情况下,基于注解的配置方式更为适用。
方式一:基于注解配置
@PluginFactory
| 配置项 | 描述 |
|---|---|
| pluginHome | 插件存放路径,必需提供 |
| packageNames | 自动扫描包名,默认为插件工厂所在包路径 |
| automatic | 插件是否自动启动,默认为 true |
| listenerClass | 插件生命周期事件监听器类对象,可选配置,若未提供则采用系统默认实现 |
| loaderFactoryClass | 插件对象加载器工厂类,可选配置,若未提供则采用系统默认实现 |