一种优雅的Golang的库插件注册加载机制-《GO开发知识笔记》

admin 2025-11-04 01:04:39 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 注册
  • 加载
  • 总结

    最近看到一个内部项目的插件加载机制,非常赞。当然这里说的插件并不是指的golang原生的可以在buildmode中加载指定so文件的那种加载机制。而是软件设计上的「插件」。如果你的软件是一个框架,或者一个平台性产品,想要提升扩展性,即可以让第三方进行第三方库开发,最终能像搭积木一样将这些库组装起来。那么就可能需要这种库加载机制。我们的目标是什么?对第三方库进行某种库规范,只要按照这种库规范进行开发,这个库就可以被加载到框架中。我们先定义一个插件的数据结构,这里肯定是需要使用接口来规范,这个可以根据你的项目自由发挥,比如我希望插件有一个Setup方法来在启动的时候加载即可。然后我就定义如下的Plugin结构。

    1. type Plugin interface{
    2. Name() string
    3. Setup(config map[string]string) error
    4. }

    而在框架启动的时候,我启动了一个如下的全局变量:

    1. var plugins map[string]Plugin

    注册

    有人可能会问,这里有了加载函数setup,但是为什么没有注册逻辑呢?答案是注册的逻辑放在库的init函数中。即框架还提供了一个注册函数。

    1. // package plugin
    2. Register(plugin Plugin)

    这个register就是实现了将第三方plugin放到plugins全局变量中。所以第三方的plugin库大致实现如下:

    1. package MyPlugin
    2. type MyPlugin struct{
    3. }
    4. func (m *MyPlugin) Setup(config map[string]string) error {
    5. // TODO
    6. }
    7. func (m *MyPlugin) Name() string {
    8. return "myPlugin"
    9. }
    10. func init() {
    11. plugin.Register(&MyPlugin)
    12. }

    这样注册的逻辑就变成了,如果你要加载一个插件,那么你在main.go中直接以 _ import的形式引入即可。

    1. package main
    2. _ import "github.com/foo/myplugin"
    3. func main() {
    4. }

    整体的感觉,这样子插件的注册就被“隐藏”到import中了。

    加载

    注册的逻辑其实看起来也平平无奇,但是加载的逻辑就考验细节了。首先插件的加载其实有两点需要考虑:

    • 配置
    • 依赖

    配置指的是插件一定是有某种配置的,这些配置以配置文件yaml中plugins.myplugin的路径存在。

    1. plugins:
    2. myplugin:
    3. foo: bar

    其实我对这种实现持保留意见。配置文件以一个文件中配置项的形式存在,好像不如以配置文件的形式存在,即以config/plugins/myplugin.yaml 的文件。这样不会出现一个大配置文件的问题。毕竟每个配置文件本身就是一门DSL语言。如果你将配置文件的逻辑变复杂,一定会有很多附带的bug是由于配置文件错误导致的。第二个说的是依赖。插件A依赖与插件B,那么这里就有加载函数Setup的先后顺序了。这种先后顺序如果纯依赖用户的“经验”,将某个插件的Setup调用放在某个插件的Setup调用之前,是非常痛苦的。(虽然一定是有办法可以做到)。更好的办法是依赖于框架自身的加载机制来进行加载。首先我们在plugin包中定义一个接口:

    1. type Depend interface{
    2. DependOn() []string
    3. }

    如果我的插件依赖一个名字为 “fooPlugin” 的插件,那么我的插件 MyPlugin就会实现这个接口。

    1. package MyPlugin
    2. type MyPlugin struct{
    3. }
    4. func (m *MyPlugin) Setup(config map[string]string) error {
    5. // TODO
    6. }
    7. func (m *MyPlugin) Name() string {
    8. return "myPlugin"
    9. }
    10. func init() {
    11. plugin.Register(&MyPlugin)
    12. }
    13. func (m *MyPlugin) DependOn() []string {
    14. return []string{"fooPlugin"}
    15. }

    在最终加载所有插件的时候,我们并不是简单地将所有插件调用Setup,而是使用一个channel,将所有插件放在channel中,然后一个个调用Setup,遇到有Depend其他插件的,且依赖插件还未被加载,则将当前插件放在队列最后(重新塞入channel)。

    1. var setupStatus map[string]bool
    2. // 获取所有注册插件
    3. func loadPlugins() (plugin chan Plugin, setupStatus map[string]bool) {
    4. // 这里定义一个长度为10的队列
    5. var sortPlugin = make(chan Plugin, 10)
    6. var setupStatus = make[string]bool
    7. // 所有的插件
    8. for name, plugin := range plugins {
    9. sortPlugin <- plugin
    10. setupStatus[name] = false
    11. }
    12. return sortPlugin, setupStatus
    13. }
    14. // 加载所有插件
    15. func SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error {
    16. num := len(pluginChan)
    17. for num > 0 {
    18. plugin <- pluginChan
    19. canSetup := true
    20. if deps, ok := p.(Depend); ok {
    21. depends := deps.DependOn()
    22. for _, dependName := range depends{
    23. if _, setuped := setupStatus[dependName]; !setup {
    24. // 有未加载的插件
    25. canSetup = false
    26. break
    27. }
    28. }
    29. }
    30. // 如果这个插件能被setup
    31. if canSetup {
    32. plugin.Setup(xxx)
    33. setupStatus[p.Name()] = true
    34. } else {
    35. // 如果插件不能被setup, 这个plugin就塞入到最后一个队列
    36. pluginChan <- plugin
    37. }
    38. }
    39. return nil
    40. }

    上面这段代码最精妙的就是使用了一个有buffer的channel作为一个队列,消费队列一方SetupPlugins,除了消费队列,也有可能生产数据到队列,这样就保证了队列中所有plugin都是被按照标记的依赖被顺序加载的。

    总结

    这种插件的注册和加载机制是非常优雅的。注册方面,巧妙使用隐式import来做插件的注册。而加载方面,巧妙使用有buffer的channel作为加载队列。参考:

    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  5