Golang中如何理解调用链的问题

Golang中如何理解调用链的问题 我是Go语言的新手,正在尝试编写一个自定义的Terraform模块(Terraform是用Go编写的)。

HashiCorp Learn Setup and Implement Read | Terraform - HashiCorp Learn

我已经完成了Go语言的入门教程,所以对指针、结构体、方法、闭包等有一些理解,但我对下面这段代码仍然感到有点困惑。我可能有点操之过急了,但我非常感谢任何帮助来理解我在这里看到的内容:

package main
import (
  "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
  "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
  "terraform-provider-hashicups/hashicups"
)
func main() {
  /////// 这就是我想理解的部分——整个调用链
  plugin.Serve(&plugin.ServeOpts{
    ProviderFunc: func() *schema.Provider {
      return hashicups.Provider()
    },
  })
}

实际情况是这样的吗?…

  1. 我调用了plugin模块的Serve()方法
  2. 我将同一个插件对象的实例传递给了它自己的Serve()方法?并且修改了插件对象的状态,对吗?有点像单例模式?
  3. 我用传入的这个结构体覆盖了插件对象的默认ServeOpts结构体?
  4. ServerOpts结构体有一个ProviderFunc字段,该字段持有一个函数。我用传入的这个函数覆盖了那个函数。我传入的函数是一个匿名函数,其定义指定它返回一个指向schema.Provider的指针,并且我在那里返回了我自己的hashicups.Provider()

ProviderFunc也是模块中的一个函数类型——我不完全理解将其设为类型的用处

github.com hashicorp/terraform-plugin-sdk/blob/da3c01cf9da9ea7444863bb54c2c31ac675d515b/plugin/serve.go#L28

	ProviderPluginName = "provider"
)

// Handshake is the HandshakeConfig used to configure clients and servers.
var Handshake = plugin.HandshakeConfig{
	// The magic cookie values should NEVER be changed.
	MagicCookieKey:   "TF_PLUGIN_MAGIC_COOKIE",
	MagicCookieValue: "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2",
}

type ProviderFunc func() *schema.Provider
type GRPCProviderFunc func() tfprotov5.ProviderServer

// ServeOpts are the configurations to serve a plugin.
type ServeOpts struct {
	ProviderFunc ProviderFunc

	// Wrapped versions of the above plugins will automatically shimmed and
	// added to the GRPC functions when possible.
	GRPCProviderFunc GRPCProviderFunc


我还有很多东西要学,但我想对这里实际发生的事情有一些了解。


更多关于Golang中如何理解调用链的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

red_888:

我将同一个插件对象的实例传递给其自身的Serve()方法?并且正在修改插件对象的状态,对吗?有点像单例模式?

这里没有 plugin 对象;plugin 只是一个命名空间,类似于 C# 中的 System,或者 Java 中的 java.lang。与 Python 没有类比,因为在 Python 中,一切都是对象。你正在将一个 *ServeOpts 值传递给一个 Serve 函数。

red_888:

  • 我是否在用这个传入的结构体覆盖插件对象的默认 ServeOpts 结构体?
  • ServerOpts 结构体有一个 ProviderFunc 字段,它持有一个函数。我是否在用传入的这个函数覆盖那个函数?我传入的函数是一个匿名函数,其定义指定它返回一个指向 schema.Provider 的指针,而我正在那里返回我自己的 hashicups.Provider()

我不太确定你所说的“覆盖”是什么意思,你是在设置字段的值,但除此之外你是正确的。

red_888:

ProviderFunc 也是模块中的一个函数类型——我不完全理解将其设为类型的用处

我能想到的为函数类型命名的原因有两个:

  1. 仅仅是为了类型本身的名称,就像 time.Duration 只是一个 int64 一样。它并没有真正的类型安全性(例如,编译器会让你写 time.Second * time.Microsecond,但这样做没有意义),但无论你在哪里看到使用 time.Duration 类型,你都知道持续时间是以纳秒为单位的。在 Go 中,你不应该看到(很多)函数期望一个毫秒参数,或者其他函数以 float64 表示秒等等……标准做法是使用 time.Duration,如 3*time.Second10*time.Minute 等。

  2. 为函数添加方法,例如,你可以让一个 func(b []byte) (int, error) 像这样实现 io.Reader 接口:

    type ReaderFunc func(b []byte) (int, error)
    
    func (f ReaderFunc) Read(b []byte) (int, error) {
        return f(b)
    }
    
    // ...
    
    _, _ = io.Copy(&bytes.Buffer{}, ReaderFunc(func(_ []byte) (int, error) {
        // 这是一个愚蠢的例子,只是为了说明
        // 函数也可以实现接口。
        return 0, io.EOF
    })
    

更多关于Golang中如何理解调用链的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个很好的问题,涉及到Go语言中的函数类型、闭包和插件架构。让我们一步步分析这段代码:

package main

import (
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
    "terraform-provider-hashicups/hashicups"
)

func main() {
    plugin.Serve(&plugin.ServeOpts{
        ProviderFunc: func() *schema.Provider {
            return hashicups.Provider()
        },
    })
}

调用链分析

你的理解基本正确,让我详细解释:

1. 函数类型定义

首先看ProviderFunc的类型定义:

type ProviderFunc func() *schema.Provider

这是一个函数类型,意味着ProviderFunc是一个类型,它可以持有任何签名匹配func() *schema.Provider的函数。

2. 结构体字段

ServeOpts结构体中:

type ServeOpts struct {
    ProviderFunc ProviderFunc  // 这里使用上面定义的类型
    // ... 其他字段
}

ProviderFunc字段可以存储任何返回*schema.Provider的函数。

3. 实际调用过程

// 1. 创建 ServeOpts 结构体实例
&plugin.ServeOpts{
    // 2. 为 ProviderFunc 字段赋值一个匿名函数
    ProviderFunc: func() *schema.Provider {
        // 3. 这个匿名函数调用 hashicups.Provider()
        return hashicups.Provider()
    },
}

4. 完整执行流程

// 伪代码展示实际执行顺序:
func main() {
    // 1. 创建匿名函数
    providerFunc := func() *schema.Provider {
        return hashicups.Provider()
    }
    
    // 2. 创建 ServeOpts 结构体
    opts := &plugin.ServeOpts{
        ProviderFunc: providerFunc,
    }
    
    // 3. 调用 plugin.Serve(),传入 opts
    plugin.Serve(opts)
    
    // 4. 在 plugin.Serve() 内部,会在需要时调用:
    // provider := opts.ProviderFunc()  // 这会执行 hashicups.Provider()
}

为什么使用函数类型?

函数类型提供了灵活性,允许延迟执行。示例说明:

package main

import "fmt"

// 定义函数类型
type StringGenerator func() string

// 接受函数类型作为参数的函数
func Process(generator StringGenerator) {
    // 在需要时才执行传入的函数
    result := generator()
    fmt.Println("Generated:", result)
}

func main() {
    // 方式1:直接传入匿名函数
    Process(func() string {
        return "Hello from anonymous function"
    })
    
    // 方式2:传入已定义的函数
    Process(GetMessage)
    
    // 方式3:带参数的函数(通过闭包)
    prefix := "Custom: "
    Process(func() string {
        return prefix + "dynamic content"
    })
}

func GetMessage() string {
    return "Hello from named function"
}

在Terraform插件中的实际应用

在你的代码中,这种模式允许:

  1. 延迟初始化hashicups.Provider()只在需要时被调用
  2. 依赖注入plugin.Serve不需要知道具体的provider实现
  3. 灵活性:可以轻松切换不同的provider实现
// 假设有多个provider
func main() {
    // 可以根据条件选择不同的provider
    useCustomProvider := true
    
    var providerFunc plugin.ProviderFunc
    
    if useCustomProvider {
        providerFunc = func() *schema.Provider {
            return hashicups.Provider()
        }
    } else {
        providerFunc = func() *schema.Provider {
            return defaultProvider()
        }
    }
    
    plugin.Serve(&plugin.ServeOpts{
        ProviderFunc: providerFunc,
    })
}

关键点总结

  1. 不是单例模式:每次调用都创建新的ServeOpts实例
  2. 不是覆盖:你是为结构体字段赋值,不是覆盖
  3. 函数作为值:在Go中,函数是一等公民,可以像其他值一样传递
  4. 闭包:匿名函数可以捕获外部变量(虽然你的例子中没有)

这种模式在Go中非常常见,特别是在需要回调函数或策略模式的场景中。

回到顶部