这篇文章主要介绍如何解决golang库插件注册加载机制的问题,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!最近看到一个内部项目的插件加载机制,非常赞。当然这里说的插件并不是指的Golang原生的可以在buildmod
这篇文章主要介绍如何解决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 pluginReGISter(plugin Plugin)
这个register就是实现了将第三方plugin放到plugins全局变量中。
所以第三方的plugin库大致实现如下:
package MyPlugintype MyPlugin struct{}func (m *MyPlugin) Setup(config map[string]string) error {// TODOfunc (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 MyPlugintype MyPlugin struct{}func (m *MyPlugin) Setup(config map[string]string) error {// TODOfunc (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 <- pluginsetupStatus[name] = false}return sortPlugin, setupStatus}// 加载所有插件func SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error {num := len(pluginChan)for num > 0 {plugin <- pluginChancanSetup := trueif deps, ok := p.(Depend); ok {depends := deps.DependOn()for _, dependName := range depends{if _, setuped := setupStatus[dependName]; !setup {// 有未加载的插件canSetup = falsebreak}}}// 如果这个插件能被setupif canSetup {plugin.Setup(xxx)setupStatus[p.Name()] = true} else {// 如果插件不能被setup, 这个plugin就塞入到最后一个队列pluginChan <- pluginreturn nil}
上面这段代码最精妙的就是使用了一个有buffer的channel作为一个队列,消费队列一方SetupPlugins,除了消费队列,也有可能生产数据到队列,这样就保证了队列中所有plugin都是被按照标记的依赖被顺序加载的。
以上是“如何解决Golang库插件注册加载机制的问题”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网GO频道!
--结束END--
本文标题: 如何解决Golang库插件注册加载机制的问题
本文链接: https://www.lsjlt.com/news/325833.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-04-05
2024-04-05
2024-04-05
2024-04-04
2024-04-05
2024-04-05
2024-04-05
2024-04-05
2024-04-04
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0