使用反射在运行时根据结构体名称动态创建实例 - Golang实现

使用反射在运行时根据结构体名称动态创建实例 - Golang实现 我需要设计一个插件和宿主架构,类似于这样:

============== package external

type AuthService interface { Auth() }

type LocalUserPlugin struct { }

func (p *LocalUserPlugin) Auth() { println(“Auth from local user plugin”) }

type RemotePluginStub struct { }

func (p *RemotePluginStub) Auth() { println("Auth from remote plugin stub ") }

============

package main

func main() { names := []string{“external.LocalUserPlugin”, “external.RemotePluginStub”}

for _, name := range names {
    t := reflect.TypeOf(name)
    o := reflect.New(t)
    e := o.Elem()
    f := e.Interface()
    s, ok := f.(AuthService)

    if !ok {
        println("bad type")
        continue
    }

    s.Auth()
}

}

===========

请注意,插件是由其他人提供的,宿主只知道包名和它提供的接口。

所以在主包(宿主)中,它需要在运行时创建特定的插件类型。

但上面的代码无法工作。

为什么 reflect.TypeOf() 对于给定的类型名称不起作用? 有什么解决办法吗?

谢谢


更多关于使用反射在运行时根据结构体名称动态创建实例 - Golang实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

我不确定你是否可以使用 reflect 来实现,但在你的情况下,我会创建一个构造函数,例如:

func NewAuthService(name string) AuthService {
   ...
}

然后在函数中创建逻辑来选择正确的实现。

更多关于使用反射在运行时根据结构体名称动态创建实例 - Golang实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。

我不太喜欢RPC/GRPC,那需要消息序列化。

我原本希望有一种方法,能通过反射直接使用二进制文件获取对象。

如果使用微软的.NET反射,同样的事情可以轻松完成。

但在Go语言的反射中,这似乎很难做到。

你的建议意味着会有一个庞大的“if … else”语句,并且我必须知道每个实现该接口的结构体的名称。

type AuthService interface {
Auth()
}

再次说明,插件是由其他公司以二进制文件形式提供的。我无法知道它们的对象。我只知道它们会实现上述接口。

name 的类型是 stringTypeOf 在这里按预期工作。(可以尝试 fmt.Println(t)

关于为 AuthService 使用不同实现的总体目标,我不确定你为什么需要在这里使用反射。这两个结构体已经实现了 AuthService 接口,因此可以传递给任何期望 AuthService 的函数。

除此之外,你还想实现什么?

因为插件来自不同公司外部供应商提供的、名为“external”包下的不同二进制文件

包“main”是我开发的主应用程序,我与外部插件之间的约定是以下接口:

type AuthService interface {
    Auth()
}

供应商只提供他们的二进制文件(不共享源代码),所以我必须在运行时获取他们的对象,以了解哪个对象实现了上述接口。

感谢。

使用标准库的插件功能已经非常接近了。

唯一的问题是,供应商很容易忘记使用 go build -buildmode=plugin 选项将他们的代码构建为插件,这导致他们的二进制文件无法用于获取符号。

实际上,plugin.Open() 内部实现的功能正是 Go 语言反射所缺失的。

为什么 Go 语言不能将其实现 plugin.Open 的方式作为公共方法公开,以便任何人都可以加载任何二进制文件来查找其中的符号(而不仅仅局限于使用 -buildmode=plugin 选项构建的二进制文件)?

Albert_Liu:

唯一的问题是,供应商很容易忘记使用 go build -buildmode=plugin 选项将其代码构建为插件,这导致他们的二进制文件无法用于获取符号。

嗯,老实说……其他插件系统也是这样工作的。人们必须知道他们正在构建一个插件,然后必须知道要公开哪些符号。

许多没有任何反射功能的语言(如 C/C++)都以我描述的方式实现插件,主系统定义通用接口,插件的创建者来实现它。

通常,一个插件应该包含:

  • 一个唯一的标识符(通常采用反向 DNS 名称的形式,如 com.my-company.audio-plugin
  • [可选] 一个版本字符串
  • 一个初始化/注册方法,加载插件的一方使用它来获取信息,然后使用该插件

感谢您的澄清。

我目前还没有听说过通过反射来逆向工程和访问不同二进制文件中的数据结构和方法。我猜您可能需要深入研究二进制文件的符号表,并使用一些调试器技术来查找和调用外部代码。

您可能想了解一下 Delve(流行的 Go 调试器)是如何做到这一点的,但在我看来,更好的方法是与您的外部提供商商定一个协议,以便远程调用第三方二进制文件。有相当多的可用选项,包括:

  • 简单的 stdin/stdout 管道
  • net/rpc
  • 消息队列(NATS,或无代理版本如 nsq
  • gRPC
  • REST
  • GraphQL

这里有一篇来自互联网上某个人的不错的文章 ;) 作为入门:Go 中的插件 · Applied Go(也展示了 net/rpc 的用法。)

只是一个(可能有点傻的)想法。你不能使用 plugin 包吗?

这样,也许每个供应商都可以提供一个插件,该插件暴露一些 API 来在你的系统中注册自己。 例如,接口可能是这样的:

const PluginName string = "com.my-company.AuthService"

func NewService() AuthService {
    ....
}

然后,在系统启动时,可以加载所有插件并创建一个类似这样的映射:

var PluginsMap map[string]func() = map[string]func(){}

func init() {
   // 加载所有插件,并将插件的名称和 NewService 函数放入映射中
}

func main() {
    var pluginName string
    // 使用某些标准来决定名称

    var svc AuthService
    if f, ok := PluginsMap[string]; ok {
        svc - f()
    } else {
        // 默认实现或 panic
    }
}

在Go中,reflect.TypeOf() 返回的是值的运行时类型,而不是根据字符串名称查找类型。你的代码中 reflect.TypeOf(name) 实际上返回的是字符串 "external.LocalUserPlugin" 的类型,而不是该名称对应的结构体类型。

要实现根据类型名称动态创建实例,需要使用Go的插件系统(plugin package)或类型注册机制。以下是两种解决方案:

方案1:使用全局注册表(推荐)

package external

import "reflect"

type AuthService interface {
    Auth()
}

// 创建类型注册表
var typeRegistry = make(map[string]reflect.Type)

func init() {
    typeRegistry["LocalUserPlugin"] = reflect.TypeOf(LocalUserPlugin{})
    typeRegistry["RemotePluginStub"] = reflect.TypeOf(RemotePluginStub{})
}

// 注册函数
func RegisterType(name string, typ reflect.Type) {
    typeRegistry[name] = typ
}

// 创建实例函数
func CreateInstance(typeName string) (interface{}, error) {
    if typ, ok := typeRegistry[typeName]; ok {
        return reflect.New(typ).Interface(), nil
    }
    return nil, fmt.Errorf("type not found: %s", typeName)
}

type LocalUserPlugin struct{}

func (p *LocalUserPlugin) Auth() {
    println("Auth from local user plugin")
}

type RemotePluginStub struct{}

func (p *RemotePluginStub) Auth() {
    println("Auth from remote plugin stub")
}

宿主代码:

package main

import (
    "external"
    "fmt"
)

func main() {
    names := []string{"LocalUserPlugin", "RemotePluginStub"}
    
    for _, name := range names {
        instance, err := external.CreateInstance(name)
        if err != nil {
            fmt.Printf("Error creating %s: %v\n", name, err)
            continue
        }
        
        if s, ok := instance.(external.AuthService); ok {
            s.Auth()
        } else {
            fmt.Printf("bad type: %s\n", name)
        }
    }
}

方案2:使用Go插件(plugin package)

插件代码:

// plugin.go
package main

import "external"

type LocalUserPlugin struct{}

func (p *LocalUserPlugin) Auth() {
    println("Auth from local user plugin")
}

// 导出变量
var Plugin external.AuthService = &LocalUserPlugin{}

编译插件:

go build -buildmode=plugin -o plugin.so plugin.go

宿主代码:

package main

import (
    "external"
    "plugin"
)

func main() {
    pluginPaths := []string{"./plugin.so"}
    
    for _, path := range pluginPaths {
        p, err := plugin.Open(path)
        if err != nil {
            println("Error loading plugin:", err.Error())
            continue
        }
        
        symbol, err := p.Lookup("Plugin")
        if err != nil {
            println("Error looking up symbol:", err.Error())
            continue
        }
        
        if s, ok := symbol.(external.AuthService); ok {
            s.Auth()
        } else {
            println("bad type")
        }
    }
}

方案3:使用反射和包路径(需要导入包)

package main

import (
    "external"
    "reflect"
    "strings"
)

func createInstance(typeName string) (interface{}, error) {
    parts := strings.Split(typeName, ".")
    if len(parts) != 2 {
        return nil, fmt.Errorf("invalid type name format")
    }
    
    // 需要确保包已经被导入
    switch parts[0] {
    case "external":
        switch parts[1] {
        case "LocalUserPlugin":
            return &external.LocalUserPlugin{}, nil
        case "RemotePluginStub":
            return &external.RemotePluginStub{}, nil
        }
    }
    
    return nil, fmt.Errorf("type not found: %s", typeName)
}

func main() {
    names := []string{"external.LocalUserPlugin", "external.RemotePluginStub"}
    
    for _, name := range names {
        instance, err := createInstance(name)
        if err != nil {
            println("Error:", err.Error())
            continue
        }
        
        if s, ok := instance.(external.AuthService); ok {
            s.Auth()
        } else {
            println("bad type")
        }
    }
}

第一种方案(全局注册表)是最常用且灵活的方式,它允许插件在初始化时自行注册,而不需要宿主预先知道所有类型。

回到顶部