PF4J:Plugin Framework for Java
目录
是什么?
不是什么?
特点
组件
主要类
流程概述
spring-pf4j
思考
功能模块化
我对pf4j的封装和使用demo
GitHub - chlInGithub/pf4jDemo: pf4j demo
是什么?
开源轻量级的插件框架。通过插件形式对系统功能进行个性化扩展。插件需要实现扩展点,扩展点由系统进行定义。
不是什么?
功能模块化加载框架。虽然介绍中描述pf4j可以将庞大的系统转化为模块系统,但依据我的实践来看,仅仅依赖pf4j只能动态加载扩展,无法动态加载完整的功能模块。
特点
- 开源轻量级的插件框架
- 简单的标记扩展点,使用interface ExtensionPoint即可将接口和抽象类定义为扩展点
- 简单的标记扩展,使用@Extension即可定义一个扩展
组件
插件 等同于 由 扩展点、扩展、生命周期行为 构成的一个集合。
Plugin | 所有插件的基类。每一个插件均由单独的classloader进行加载,避免冲突。 |
PluginManager | 对插件进行切面化管理,如loading, starting, stopping。已提供3中实现,JarPluginManager, ZipPluginManager, DefaultPluginManager(jar_zip)。也可自行实现个性化pluginManager,需要实现AbstractPluginManager。 |
PluginLoader | 加载插件需要的所有信息,如class |
ExtensionPoint | 扩展点 |
Extension | 扩展,即扩展点的实现 |
主要类
PluginManager | pf4j通过PluginManager向外提供plugin管理能力,如生命周期控制、获取扩展实例 |
PluginWrapper | plugin的包装类 |
ManifestPluginDescriptorFinder | 从(支持jar\zip\目录)manifest文件读取插件信息,如Plugin-Id(用于避免重复load)、Plugin-Version、Plugin-Class |
PluginLoader | 用于load plugin需要的所有信息,每个pluginPath对应一个pluginLoader,为每个plugin提供独立的classloader。 如JarPluginLoader |
PluginClassLoader | 自定义的classLoader,修改了loadClass的逻辑。每个plugin对应一个PluginClassLoader实例 |
PluginState | 插件生命周期期间的各种状态 |
DependencyResolver | 插件间依赖关系分解器。一系列插件之间构建依赖图,唯一入口resolve可以返回依赖分解结果,如插件之间是否存在循环依赖、能否找到依赖的插件、插件依赖版本是否正确等。 |
LegacyExtensionFinder | 读取plugin包中META-INF/extensions.idx文件,获取扩展信息和实例。 |
生命周期概述
加载插件过程 | MF文件中插件描述-->插件独立classLoader-->插件间依赖解析-->插件已解析状态 |
开始插件过程 | 加载Plugin实现并生成实例-->调用Plugin.start()-->插件已开始状态 |
获取某个扩展点实现类的实例 | |
停止插件 | 插件状态变为停止 |
卸载插件 | 从集合中清除,关闭classLoader |
spring-pf4j
将扩展注册为spring ioc bean
思考
如何支持租户场景?
pf4j非线程安全,允许不同租户维护各自插件的场景,需要在pf4j基础上再包裹一层,一方面增加线程安全,一方面维护租户与插件、扩展的关系。
需要以一种路径规范存放插件jar,如下图:
如何支持插件依赖的jar?
- 方式1:系统列举出支持的jar集合,插件依赖其中的jar。在demo中已体现该场景,插件正常运行。
如图为插件的pom依赖,插件jar中不包含依赖的任何jars。
插件jar放到上图的租户路径下,在系统中运行Pf4jTest.main,插件扩展运行正常。因为系统的classpath中包含common-lang jar,所以classloader可以找到common-lang中class。
扩展运行结果如下,可以清晰看出,两个插件的classloader是独立的。
- 方式2:插件jar内部包含依赖的jars
- 方式2.1:jdk的classloader不支持嵌套jar,需参考spring LaunchedURLClassLoader或JarClassLoader(http://www.jdotsoft.com/JarClassLoader.php)。在不改变pf4j源码情况下,无法实现该方案,因为pf4j使用URLClassLoader。
- 方式2.2:解压插件jar-->pf4j加载插件jar-->修改生成的classloader的扫描范围,添加依赖jars路径-->启动插件jar
- 方式3:系统根据插件jar的(maven)依赖去下载依赖的jar
功能模块化
基本能力:系统功能进行模块划分,模块可动态加载和卸载,模块独立且隔离,模块间资源不产生冲突。
设计思路:独立的classloader(修改加载class的顺序,自有范围-->双亲委派)、springcontext、生命周期管理