Go plugins 插件

插件开发

动态加载plugins,实现功能扩展、热加载. 但是存在一个问题,编译必须是同一个go版本

插件弱点

原生插件的弱点

  • 编译的 Go 版本必须完全一致 - 事实上这个插件都可以不是由同一个人编写,要求编译的 Go 版本一致显然有点要求太高了
  • 双方依赖的公共第三方库版本必须完全一致
  • GOPATH 也得保持一致 - 不过这一点可以在编译时候使用 trimpath 参数解决
  • 插件加载之后无法卸载

项目目录

 1.
 2├── Makefile
 3├── README.md
 4├── go.mod
 5├── main.go
 6└── plugins
 7    ├── eng
 8    │   └── speaker.go
 9    └── vie
10        └── speaker.go

编译项目

1make

运行demo

1go run main.go vietnamese
2
3or
4
5go run main.go english

源码

  1. main.go
 1package main
 2
 3import (
 4	"errors"
 5	"fmt"
 6	"os"
 7	"plugin"
 8	"strings"
 9)
10
11type Speaker interface {
12	Speak() string
13}
14
15func main() {
16	err := run(os.Args)
17	if err != nil {
18		fmt.Println(err.Error())
19		os.Exit(1)
20	}
21}
22
23func run(args []string) error {
24	lang := "english"
25	if len(args) == 2 {
26		lang = args[1]
27	}
28	var mod string
29	switch lang {
30	case "english":
31		mod = "./plugins/eng/speaker.so"
32	case "vietnamese":
33		mod = "plugins/vie/speaker.so"
34	default:
35		return errors.New("this speakerName is not supported")
36	}
37
38	plugin, err := plugin.Open(mod)
39	if err != nil {
40		return err
41	}
42
43	speaker, err := lookUpSymbol[Speaker](plugin, "Speaker")
44	if err != nil {
45		return err
46	}
47
48	speakerName, err := lookUpSymbol[string](plugin, "SpeakerName")
49	if err != nil {
50		return err
51	}
52	fmt.Printf("%s says \"%s\" in %s", *speakerName, (*speaker).Speak(), strings.Title(lang))
53	return nil
54}
55
56func lookUpSymbol[M any](plugin *plugin.Plugin, symbolName string) (*M, error) {
57	symbol, err := plugin.Lookup(symbolName)
58	if err != nil {
59		return nil, err
60	}
61	switch symbol.(type) {
62	case *M:
63		return symbol.(*M), nil
64	case M:
65		result := symbol.(M)
66		return &result, nil
67	default:
68		return nil, errors.New(fmt.Sprintf("unexpected type from module symbol: %T", symbol))
69	}
70}
  1. Makefile
1build-plugins:
2	$(foreach file, $(wildcard ./plugins/*), go build -buildmode=plugin -o $(file)/speaker.so $(file)/speaker.go;)
  1. plugins/eng/speaker.go
 1package main
 2
 3type speaker struct {
 4}
 5
 6func (s *speaker) Speak() string {
 7	return "hello"
 8}
 9
10// Exported
11var Speaker speaker
12var SpeakerName = "Alice"
  1. plugins/vie/speaker.go
 1package main
 2
 3type speaker struct {
 4}
 5
 6func (s *speaker) Speak() string {
 7	return "xin chào"
 8}
 9
10// Exported
11var Speaker speaker
12var SpeakerName = "Anh Thư"

源码