一种优雅的Golang的库插件注册加载机制
一种优雅的Golang的库插件注册加载机制
你好,我是轩脉刃。
最近看到一个内部项目的插件加载机制,非常赞。当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制。而是软件设计上的「插件」。如果你的软件是一个框架,或者一个平台性产品,想要提升扩展性,即可以让第三方进行第三方库开发,最终能像搭积木一样将这些库组装起来。那么就可能需要这种库加载机制。
我们的目标是什么?对第三方库进行某种库规范,只要按照这种库规范进行开发,这个库就可以被加载到框架中。
我们先定义一个插件的数据结构,这里肯定是需要使用接口来规范,这个可以根据你的项目自由发挥,比如我希望插件有一个Setup方法来在启动的时候加载即可。然后我就定义如下的Plugin结构。
type Plugin interface{
Name() string
Setup(config map[string]string) error
}
而在框架启动的时候,我启动了一个如下的全局变量:
var plugins map[string]Plugin
注册
有人可能会问,这里有了加载函数setup,但是为什么没有注册逻辑呢?
答案是注册的逻辑放在库的init函数中。
即框架还提供了一个注册函数。
// package plugin
Register(plugin Plugin)
这个register就是实现了将第三方plugin放到plugins全局变量中。
所以第三方的plugin库大致实现如下:
package MyPlugin
type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
// TODO
}
func (m *MyPlugin) Name() string {
return "myPlugin"
}
func init() {
plugin.Register(&MyPlugin)
}
这样注册的逻辑就变成了,如果你要加载一个插件,那么你在main.go中直接以 _ import的形式引入即可。
package main
_ import "github.com/foo/myplugin"
func main() {
}
整体的感觉,这样子插件的注册就被“隐藏”到import中了。
加载
注册的逻辑其实看起来也平平无奇,但是加载的逻辑就考验细节了。
首先插件的加载其实有两点需要考虑:
- 配置
- 依赖
配置指的是插件一定是有某种配置的,这些配置以配置文件yaml中plugins.myplugin的路径存在。
plugins:
myplugin:
foo: bar
其实我对这种实现持保留意见。配置文件以一个文件中配置项的形式存在,好像不如以配置文件的形式存在,即以config/plugins/myplugin.yaml 的文件。
这样不会出现一个大配置文件的问题。毕竟每个配置文件本身就是一门DSL语言。如果你将配置文件的逻辑变复杂,一定会有很多附带的bug是由于配置文件错误导致的。
第二个说的是依赖。插件A依赖与插件B,那么这里就有加载函数Setup的先后顺序了。这种先后顺序如果纯依赖用户的“经验”,将某个插件的Setup调用放在某个插件的Setup调用之前,是非常痛苦的。(虽然一定是有办法可以做到)。更好的办法是依赖于框架自身的加载机制来进行加载。
首先我们在plugin包中定义一个接口:
type Depend interface{
DependOn() []string
}
如果我的插件依赖一个名字为 “fooPlugin” 的插件,那么我的插件 MyPlugin就会实现这个接口。
package MyPlugin
type MyPlugin struct{
}
func (m *MyPlugin) Setup(config map[string]string) error {
// TODO
}
func (m *MyPlugin) Name() string {
return "myPlugin"
}
func init() {
plugin.Register(&MyPlugin)
}
func (m *MyPlugin) DependOn() []string {
return []string{"fooPlugin"}
}
在最终加载所有插件的时候,我们并不是简单地将所有插件调用Setup,而是使用一个channel,将所有插件放在channel中,然后一个个调用Setup,遇到有Depend其他插件的,且依赖插件还未被加载,则将当前插件放在队列最后(重新塞入channel)。
var setupStatus map[string]bool
// 获取所有注册插件
func loadPlugins() (plugin chan Plugin, setupStatus map[string]bool) {
// 这里定义一个长度为10的队列
var sortPlugin = make(chan Plugin, 10)
var setupStatus = make[string]bool
// 所有的插件
for name, plugin := range plugins {
sortPlugin <- plugin
setupStatus[name] = false
}
return sortPlugin, setupStatus
}
// 加载所有插件
func SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error {
num := len(pluginChan)
for num > 0 {
plugin <- pluginChan
canSetup := true
if deps, ok := p.(Depend); ok {
depends := deps.DependOn()
for _, dependName := range depends{
if _, setuped := setupStatus[dependName]; !setup {
// 有未加载的插件
canSetup = false
break
}
}
}
// 如果这个插件能被setup
if canSetup {
plugin.Setup(xxx)
setupStatus[p.Name()] = true
} else {
// 如果插件不能被setup, 这个plugin就塞入到最后一个队列
pluginChan <- plugin
}
}
return nil
}
上面这段代码最精妙的就是使用了一个有buffer的channel作为一个队列,消费队列一方SetupPlugins,除了消费队列,也有可能生产数据到队列,这样就保证了队列中所有plugin都是被按照标记的依赖被顺序加载的。
总结
这种插件的注册和加载机制是非常优雅的。注册方面,巧妙使用隐式import来做插件的注册。而加载方面,巧妙使用有buffer的channel作为加载队列。
欢迎关注公众号: 轩脉刃的刀光剑影
一种优雅的Golang的库插件注册加载机制的更多相关文章
- [osg]osgDB的加载机制,使用3DS插件做参考(转,整理现有osgDB资料)
参考:http://blog.sina.com.cn/s/blog_7cdaf8b60102uzu3.html http://blog.csdn.net/wang15061955806/article ...
- bootstrap table插件动态加载表头
这篇文章主要为大家详细介绍了bootstrap table插件动态加载表头,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 bootstrap的table属性已经很熟悉了,最近遇到一个问题,犹豫 ...
- 插件化框架解读之so 文件加载机制(四)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...
- OS10.11系统下 安装cocoapods 以及 安装cocoapods-xcode-plugin-master插件来加载三方框架
http://www.cnblogs.com/cheng923181/p/4883476.html OS10.11系统下 安装cocoapods 以及 安装cocoapods-xcode-plugin ...
- 使用 .NET Core 3.0 的 AssemblyLoadContext 实现插件热加载
一般情况下,一个 .NET 程序集加载到程序中以后,它的类型信息以及原生代码等数据会一直保留在内存中,.NET 运行时无法回收它们,如果我们要实现插件热加载 (例如 Razor 或 Aspx 模版的热 ...
- 深入理解LINUX下动态库链接器/加载器ld-linux.so.2
[ld-linux-x86-64.so.2] 最近在Linux 环境下开发,搞了好几天 Compiler 和 linker,觉得有必要来写一篇关于Linux环境下 ld.so的文章了,google上搜 ...
- antd图标库按需加载的插件实现
前景概要 antd是阿里出品的一款基于antd的UI组件库,使用简单,功能丰富,被广泛应用在中台项目开发中,虽然也出现了彩蛋事故,但不能否认antd本身的优秀,而我们公司在实际工作中也大量使用antd ...
- 自定义的插件如何加载到Qt Designer中(详细)
要想在Qt Designer中使用自定义控件,必须要使Qt Designer能够知道我们的自定义控件的存在.有两种方法可以把新自定义控件的信息通知给Qt Designer:“升级(promotion) ...
- Javascript - LayUI库的流加载
LayUI库的流加载 用的LayUI-v2.2.45,将整个包解压缩后添加到项目,引入两个文件即可,不需要引入Jquery,此库自带: <link href="../js/layui- ...
随机推荐
- 【论文考古】Training a 3-Node Neural Network is NP-Complete
今天看到一篇1988年的老文章谈到了训练一个简单网络是NPC问题[1].也就是下面的网络结构,在线性激活函数下,如果要找到参数使得输入数据的标签估计准确,这个问题是一个NPC问题.这个文章的意义在于宣 ...
- 自学linux(常用命令)STEP3
tty tty 可以查看当前处于哪一个系统中. 比如我在图形化界面输入 tty: alt+ctrl+F3切换到命令行: linux命令 linux命令,一般都是 命令+选项+参数,这种格式,为了防止选 ...
- Grafana v8.3.3 & jmeter-influxdb2-backend
1. 说明 接上篇文章,今天继续聊Grafana & influxdb2-backend. 2. Grafana v8.3.3安装 下载rpm包 wget https://dl.grafana ...
- IO_FILE——leak 任意读
在堆题没有show函数时,我们可以用 IO_FILE 进行leak,本文就记录一下如何实现这一手法. 拿一个输出函数 puts 来说,它在源码里的表现形式为 _IO_puts . _IO_puts ( ...
- Python 面向对象编程之封装的艺术
1. 面向对象编程 OOP ( Object Oriented Programming) 即面向对象编程. 面向对象编程是一种编码思想,或是一种代码组织方式.如同编辑文章时,可以选择分段.分节的方式 ...
- Json:Java对象和Json文本转换工具类
Json 是一个用于 Java 对象 和 Json 文本 相互转换的工具类. 安装 下载源码 git clone https://github.com/njdi/durian.git 编译源码 cd ...
- 还在争论WPS、Office哪个更好用?这款云办公工具才是真的香!
最近,金山WPS更新狠狠的刷了一波存在感.尤其是xlookup函数,着实是有被惊艳到,也让大家看到了国产办公软件的进步.甚至有人认为WPS已经超越了传统的办公软件--微软office.WPS的优点固然 ...
- 深度学习分类问题中accuracy等评价指标的理解
在处理深度学习分类问题时,会用到一些评价指标,如accuracy(准确率)等.刚开始接触时会感觉有点多有点绕,不太好理解.本文写出我的理解,同时以语音唤醒(唤醒词识别)来举例,希望能加深理解这些指标. ...
- 小谈C#泛型
(一)泛型的由来 泛型是CLR2.0新增的,泛型兼具可重用性,类型安全和效率.泛型的本质就是在程序第一次编译的为IL代码的时候,就会帮我们生成一个占位符,在git即时编译的时候,就会把占位符替换为真实 ...
- Oracle 添加用户并赋权,修改密码,解锁,删除用户的方法
转至:https://www.jb51.net/article/20367.htm 添加用户(随着用户的创建,自动产生与用户同名的schema) CREATE USER "TESTER&qu ...