使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)
AI Summary
哪个男孩不想拥有一个自己的插件系统?(x)话说回来,这个我已经计划好久了,不过一直在学其他的东西,刚开始想的是微服务+注册中心,不过对个人来说写这个实在太消耗精力了。在群里大佬的推荐下我感觉可以用 pf4j 试一试,不过毕竟是新玩意儿,加上...它文档是真的抽象,更不必说中文互联网从头翻到尾都是依托答辩,所以最后的效果是经过了大半天的研究,断点,看源码,然后尝试归纳出来的,这东西本来以为前端注册和加载组件是难点,结果是熟悉框架(说实话用 Spring 和反射自己实现都没这么痛苦)...不过既然已经弄出来了,那这篇文章可能是你能找到的最详细的一步一步手把手整合框架的教程了。
(虽然我感觉是因为我太菜了 😭 搞这个才这么麻烦,大佬轻喷,有更好的想法恳请赐教)
这篇文章是整个插件系统构建环节的第一步(插件实例注入,Restful API 管理,动态加载远程组件),全文较长因此分开书写,再加上本来后两部分还在 dev 分支,还没有深入测试
了解 pf4j
pf4j,也就是 Plugin Framework for Java,它足够轻量,足够具有拓展性,官方的介绍是:
PF4J is an open source (Apache license) lightweight (around 100 KB) plugin framework for java, with minimal dependencies (only
slf4j-api
&java-semver
) and very extensible (seePluginDescriptorFinder
andExtensionFinder
).
官方文档也是很好心的给出了 4 个扩展和非常简洁的一段使用方法,大意就是确定一个接口(抽象类)作为扩展接口,然后将扩展类打上 @Extension
注解
- pf4j-update (update mechanism for PF4J)
- pf4j-spring (PF4J - Spring Framework integration)
- pf4j-web (PF4J in web applications)
- pf4j-wicket (Wicket Plugin Framework based on PF4J)
No XML, only Java.
You can mark any interface or abstract class as an extension point (with marker interface
ExtensionPoint
) and you specified that an class is an extension with@Extension
annotation.
集成框架
在 pf4j-spring 的 github 仓库提供了一个示例(How to use),我们沿着这个思路分析其用法,于是尝试出了注册方法,那咱们就开始吧:先把 github 仓库贴上:pf4j-spring
安装依赖
首先到 Maven 中央仓库找到最新版本:pf4j-spring/0.9.0
复制到 pom.xml
即可:
java
我们首先为其创建配置文件:
java
这里的 SpringPluginManager,稍微追一下源码可以发现,其首先继承了默认管理接口(因为这本身也是一个扩展),提供了应用程序上下文及其 get set,提供一个 @PostConstruct
方法,初始化后会调用加载和运行插件,通过 BeanFactory 将应用上下文中的类信息实例化并注入,以实现插件的效果。
这里与 DefaultPluginManager 不一样的,其提供了初始化行为,无需我们手动执行 runner
我们不难在 DefaultPluginManager 中发现,可以在 application.yml
中指定插件目录
yml
我们也可以同时开一下调试级别日志方便我们进行排查:
yml
创建扩展接口
按理来说,我们创建一个统一扩展接口,插件继承接口实现自定义方法,再提供主类集成 pf4j 的接口实现生命周期方法就可以圆满解决了,不过当你想在新项目中引用原来的扩展接口的时候,如何引用就成了问题...
先不着急,我们可以先写一下扩展接口,比如我命名为 BlogPlugin
java
问题来了,
如果你选择复制粘贴,那么 jvm 会说:我看这两个接口也不一样啊,这加载的类不是一模一样也可以说毫不相关了()
如果你选择创建一个共享包,那么就是以下的步骤:
创建一个新的 maven 项目,src/main/java/包名
还是方才相同的内容,然后去安装好依赖:
xml
共享包的优雅解决
插件系统肯定要人人开发,方便贡献嘛,因此我们最好还是打包并开放,而 Github Packages 便成为了不错的选择:
还是先看文档捏:使用 Maven 发布 Java 包
首先在你的 pom.xml
中添加发布包相关信息:
xml
发布包的全过程都是由 Github Actions 实现的,我们可以创建 .github/workflows/deploy.yml
(deploy 就是个名字,什么都可以)
yml
其中的 permissions 十分重要,要赋予当前 token packages 的写入权限,这样我们才能发布包
add commit push 一气呵成,不出意外的话,你的包就发布成功了
如果这个包是私有读的,我们需要配置好自己的 token 以获得权限,在你的家目录 .m2/settings.xml
(没有就自己创建)
xml
于是我们在项目中安装,我们就可以继续啦。
创建插件
我们顺利到了创建插件这一步,还是创建新的 maven 项目,安装需要的依赖(与上一个几乎相同)
首先我们创建方法实现类:我们用一个网易云请求举例(其实根本没请求,就起了个名字)
java
然后再提供插件主类以实现生命周期方法和注册应用上下文
java
这里的创建上下文挺重要的,我试了好久,直接包注册比较稳妥,当然按照官方方法也没有问题。
下面,为了标识这个包的信息,我们创建 resources/plugin.properties
properties
这里提供的要是继承了 SpringPlugin 的主类而不是接口实现类。
由于其要求 properties 文件在 jar 包根目录,我们简单配置一下即可
xml
于是便可以大笔一挥,打包咯:
shell
扩展接口
其实到这步,通过 debug 日志你就可以发现插件已经通过豆工厂(bushi)成功实例化了,不过还记得我们之前提供的 getEndPoint
方法吗,我们为了让其调用更方便,于是便将其动态注册到 SpringMVC 端点上
由于我们根据应用上下文来注册插件,同样地,我们也可以在 ContextRefreshedEvent
触发时动态修改当前的路由,于是我们可以创建 PluginRouteRegistrar
,我们分析一下它要完成什么:
首先要有之前依赖注入的插件管理示例,要有应用上下文获取,触发时的处理,我们让其实现 ApplicationContextAware
, ApplicationListener<ContextRefreshedEvent>
,于是就有了:
java
为了避免重复注入,我们维护一个 Set,注册成功即填入。
我们重写 onApplicationEvent
方法,通过 getExtensions()
获取我们之前打注解的实现类并实例化,于是我们便可以拿到他的方法。
对于注册,我们可以用 SpringMVC 提供的 RequestMappingHandlerMapping
,将端点与其方法绑定,实现了动态加载路由
总结
大体的思路就是共享类,各个插件公共实现方法,由注解找到其对应类并实例化,调用 get 方法拿到端点和方法,注册到 Spring。
但是到这里充其量只是提供了一个思路,接下来我们要实现上传,动态加载和卸载,以及前端远程加载打包的 js,最终实现想要的效果,路漫漫其修远兮,前后端还有很长的路...
《使用 pf4j-spring 实现插件注入和 api 接口动态注册 | 插件系统构建(上)》采用知识共享署名 4.0 国际许可协议
转载请注明出处并遵循 CC BY 许可协议条款