golang text/template+plugin:在运行过程中生成代码,编译插件,并动态加载使用

遇到这样的场景:需要按名来新建对应的struct的实例,并进行调用struct中的函数等后续操作
由于无法事先得知所有可能用到的名称,且在代码中预先写死所有可能出现的情况既不够简洁,也没有扩展性
最开始尝试使用各种反射方法无果,后来尝试在运行过程用text/template包生成代码,再用plugin包将代码编译成.so插件,动态加载到主程序中使用,勉强能用

使用text/template生成代码

此处以生成一个函数的代码为例,根据{{.anyName}}{{.content}}这两个参数来分别指定了函数的名称、函数中输出的内容
改变这两个参数的值,即可生成名称与功能不同的函数代码

示例代码

package main

import (
    "bytes"
    "fmt"
    "text/template"
)

func testTemplate(anyName, content interface{}) string {
    //生成代码所用的模板,其中要填充的参数处写成{{.参数名}}格式
    code_temp := `
func {{.anyName}}_function(){
    fmt.Println("{{.anyName}}")
    fmt.Println("{{.content}}")
}`
    //使用map存放参数
    args := map[string]interface{}{"anyName": anyName, "content": content}
    tmpl, err := template.New("test").Parse(code_temp)
    if err != nil {
        fmt.Println("处理代码模板出错")
        return ""
    }
    //使用一个io.Writer接收结果
    buf := bytes.NewBufferString("")
    tmpl.Execute(buf, args)
    return buf.String()
}

func main() {
    output := testTemplate("name111",2222)
    fmt.Println(output)
}

输出结果

func name111_function(){
        fmt.Println("name111")
        fmt.Println("2222")
}

使用plugin生成与加载插件

示例代码

在项目根目录下建一个子文件夹./usePlugin,来存放插件的代码,插件代码如下

func myfunction(sometext string,somenum int){
    fmt.Println(sometext,somenum)
}

把代码编译成插件

使用以下命令,其中-o参数指定编译出来的插件名称,最后一个参数为插件代码目录

go build -buildmode=plugin -o plugin_1.so ./usePlugin/

加载插件使用

此处示例了调用插件中的函数的方式(也可以调用插件中的变量)

func loadPLugin(){
    //加载插件
    plug,err:=plugin.Open("./plugin_1.so")
    if err!=nil{
        fmt.Println("load plugin err:",err)
        return
    }
    //按名获取需要使用的插件中的函数(或变量)
    pFunc,err:=plug.Lookup("myfunction")
    if err!=nil{
        fmt.Println("lookup err:",err)
        return
    }
    //获取后,需要先进行类型断言,才能使用该函数(或变量)
    pFunc.(func(string,int))("1111",22)
}

输出结果

1111 22

结合使用

在程序运行过程中结合使用template与plugin动态生成插件

  • 使用文件作为template.Execute中的io.Writer,即可将生成的代码写入文件中,作为生成插件的源代码
  • 使用os.exec,在go程序中执行命令行,来完成编译插件的操作

优缺点分析

优点

  • 扩展性

缺点&注意事项

  • 这个包还不够完善,官方不推荐使用
  • 插件编译并加载需要一定时间(数秒)
  • 需要go 1.8及以上版本, 且插件和主程序需要使用相同的go版本
  • 目前只支持linux和mac平台,暂未支持windows
  • 插件被加载后无法卸载
    – 例:加载插件1,其中定义了名为aaa的struct,再加载同样定义了名为aaa但结构不同的struct的插件2,则产生命名冲突报错

个人解决方式:若想达成通过加载插件更新struct定义的效果,则事先应将该struct定义在一个包下再导入使用,例如package1.aaa;生成插件时,每次生成随机新包名,例如package2,在该包下定义新结构的struct,在插件代码中从该新包导入struct,此时导入的struct即为package2.aaa,不再产生命名冲突

  • 在一次运行过程中,若使用同一目录下的代码多次生成插件,即使目录中的代码已有变化,plugin仍然会将前后两次编译出的插件视为同一个插件,导致出现报错;尝试根据几个issue中对类似问题的解答指定生成的插件标识符,但又会出现其他问题;因此个人建议不同的插件源代码存放在不同名的目录中

个人解决方式:每次生成随机新目录名,拷贝一份完整代码至新目录下用于生成插件

扩展

https://github.com/hashicorp/go-plugin
此外,还有另一个第三方的插件库hashicorp/go-plugin,通过grpc方式实现插件机制,但代码实现上相对更复杂

参考链接

https://blog.csdn.net/EDDYCJY/article/details/119745666
https://tonybai.com/2021/07/19/understand-go-plugin/
https://segmentfault.com/a/1190000041589675

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值