Go1.8之后支持插件机制,能够动态加载代码。Grafana是开源可视化监控平台,后端是用Go语言编写的,是非常流行的Go语言开源项目,该项目也是基于插件机制,让用户可以下载安装相应的数据库插件。本文介绍插件机制及平台支持情况,如何创建、构建应用以及如何加载插件。
插件机制
Go插件能用于很多场景,基于插件可以把系统分解为通用引擎,容易独立开发和测试。插件都遵循严格接口规范,职责明确。程序可以使用不同插件进行组合,甚至同时使用同一插件的不同版本。主程序和插件之间清晰的界限促进了松耦合和关注点分离。
Go1.8引入新的"plugin"包,提供了Open函数加载共享库返回Plugin对象。插件对象有Lookup函数返回Symbol(interface{}),它可以对插件暴露的函数或变量进行类型断言。官方文档描述Symbol:A Symbol is a pointer to a variable or function.
插件包目前仅支持Linux系统,对于其他OS需要通过其他方式进行实现,官方文档描述如下:
Currently plugins are only supported on Linux, FreeBSD, and macOS. Please report any issues.
插件示例
Go插件与正常包一样,也可以像正常包一样使用它,仅当把它编译为插件才会成为插件。
创建插件
下面实现一个简单功能插件,并在应用中调用其他暴露方法。
package main
import "fmt"
var V int
func F() {
fmt.Printf("Hello, number %d\n", V)
}
非常简单,与正常代码一样,这里暴露了变量V和F()函数。
-buildmode=plugintyp_plugin.so
go build -buildmode=plugin -o ../typ_plugin.so
执行命令,需要安装gcc编译环境,正常会在上级目录生成相应typ_plugin.so库文件。
加载插件
加载插件需要知道目标插件的位置(*.so共享库的位置),可以通过下面几种方法实现:
- 通过命令行参数指定
- 设置环境变量
- 使用配置文件
- 使用已知目录
filepath.Glob("plugins/*.so").soplugin.Open(filename)
在下面的例子中,程序期望在当前工作目录下有一个名为“plugins”的子目录,并加载它找到的所有插件。
下面示例展示如何加载指定目录下所有插件:
package main
import (
"fmt"
"plugin"
"path/filepath"
)
func main() {
all_plugins, err := filepath.Glob("plugins/*.so")
if err != nil {
panic(err)
}
for _, filename := range (all_plugins) {
fmt.Println(filename)
p, err := plugin.Open(filename)
if err != nil {
panic(err)
}
}
}
调用插件
Lookup
Lookup searches for a symbol named symName in plugin p. A symbol is any exported variable or function. It reports an error if the symbol is not found. It is safe for concurrent use by multiple goroutines.
简单描述:基于名称进行查找,查找到返回symbol,否则返回错误。插件支持多个协程并发调用。
下面示例首先加载前面创建的插件,然后查找插件中暴露的变量和方法,然后给变量赋值并调用方法。
package main
import(
"plugin"
)
func main() {
p, err := plugin.Open("typ_plugin.so")
if err != nil {
panic(err)
}
v, err := p.Lookup("V")
if err != nil {
panic(err)
}
f, err := p.Lookup("F")
if err != nil {
panic(err)
}
// v执行类型断言,然后取指针,给其赋值
*v.(*int) = 8
// f推理断言为函数并执行
f.(func())()
}
总结
plugin包给编写复杂Go应用提供了很好的机制,通常编程接口很简单,插件可以基于接口有不同复杂实现。应用动态加载插件,让程序更灵活、易扩展。