• 在Golang中依赖注入-wire篇



    一、依赖注入是什么?

    有时候一个结构体非常复杂,包含了非常多各种类型的属性,这些属性又包含了更多的属性,当我们创建这样一个结构体时需要编写大量的代码。面向接口编程可以让我们的代码避免耦合更具扩展性,但统一更换接口实现时需要大范围的修改代码。

    依赖注入帮助我们解决类似的问题,依赖注入框架能够自动解析依赖关系,帮助我们自动构建结构体实例。依赖注入可以对接口注入实例,让整个代码系统不用关注具体的接口实现。

    由于Go语言静态的特性,依赖注入在Go中应用并不广泛,主要有两种实现方式:代码生成和反射。

    wire[1]是 Google 开源的一个依赖注入工具,它使用代码生成的方式实现。我们只需要在一个特殊的go文件中告诉wire类型之间的依赖关系,它会自动帮我们生成代码,帮助我们创建指定类型的对象,并组装它的依赖。

    二、安装

    如上所述,wire利用代码生成来实现依赖注入,所以我们需要安装wire命令到PATH中

    go install github.com/google/wire/cmd/wire@latest
    
    • 1

    执行wire -h 有如下显示说明安装成功

    $ wire -h
    Usage: wire <flags> <subcommand> <subcommand args>
    
    Subcommands:
     check            print any Wire errors found
     commands         list all command names
     diff             output a diff between existing wire_gen.go files and what gen would generate
     flags            describe all known top-level flags
     gen              generate the wire_gen.go file for each package
     help             describe subcommands and their syntax
     show             describe all top-level provider sets
    
    
    Use "wire flags" for a list of top-level flags
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.快速入门

    设计一个程序,其中 Event依赖Greeter,Greeter依赖Message

    type Message string
    
    func NewMessage() Message {
     return Message("Hi there!")
    }
    
    type Greeter struct {
     Message Message
    }
    
    func NewGreeter(m Message) Greeter {
     return Greeter{Message: m}
    }
    
    func (g Greeter) Greet() Message {
     return g.Message
    }
    
    type Event struct {
     Greeter Greeter // <- adding a Greeter field
    }
    
    func NewEvent(g Greeter) Event {
     return Event{Greeter: g}
    }
    
    func (e Event) Start() {
     msg := e.Greeter.Greet()
     fmt.Println(msg)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述
    如果运行Event需要逐个构建依赖,代码如下

    func main() {
        message := NewMessage()
        greeter := NewGreeter(message)
        event := NewEvent(greeter)
    
        event.Start()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.两个概念

    正式开始前需要先了解一下 wire 当中的两个概念:provider 和 injector

    Provider
    Provider 你可以把它理解成工厂函数,这个函数的入参是依赖的属性,返回值为新一个新的类型实例

    如下所示都是 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。

    func NewMessage() Message {
     return Message("Hi there!")
    }
    
    func NewGreeter(m Message) Greeter {
     return Greeter{Message: m}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    不过需要注意的是在 wire 中不能存在两个 provider 返回相同的组件类型。即如下两个函数不能同时存在

    func NewMessage1() Message {
     return Message("Hi there!")
    }
    
    func NewMessage2() Message {
     return Message("Hi there!")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Injector

    我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系

    如下代码,我们把Event、Greeter、Message 的工厂函数(provider)一股脑塞入wire.Build()中,代表着构建 Event依赖Greeter、Message。我们不必关心Greeter、Message之间的依赖关系,wire会帮我们处理

    // wire.go
    func InitializeEvent() Event {
        wire.Build(NewEvent, NewGreeter, NewMessage)
        return Event{}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.使用wire生成代码

    当准备好provider 和 injector之后,通过 wire 命令可以自动生成一个完整的函数。如果 wire.go 不再当前路径下,也可以指定包名

    # 等价 wire ./internal 
    $ wire gen ./internal
    wire: github.com/liangwt/note/golang/demo/wire/internal: wrote /golang/demo/wire/internal/wire_gen.go
    
    • 1
    • 2
    • 3

    在执行完wire命令之后就会生成wire_gen.go,它的内容就是构建 Event。我们需要连同wire_gen.go一起提交到版本控制系统里

    // Code generated by Wire. DO NOT EDIT.
    
    //go:generate go run github.com/google/wire/cmd/wire
    //go:build !wireinject
    // +build !wireinject
    
    package internal
    
    // Injectors from wire.go:
    
    func InitializeEvent() Event {
     message := NewMessage()
     greeter := NewGreeter(message)
     event := NewEvent(greeter)
     return event
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    此时我们就可以直接在main里这样用,省去逐个构建依赖的麻烦

    func main() {
     event := internal.InitializeEvent()
     event.Start()
    }
    
    • 1
    • 2
    • 3
    • 4

    小技巧

    可以在wire.go第一行加入 //+build wireinject (与//go:build wireinject等效)注释,确保了这个文件在我们正常编译的时候不会被引用

    而 wire . 生成的文件 wire_gen.go 会包含 //+build !wireinject 注释,正常编译的时候,不指定 tag 的情况下会引用这个文件

    //go:build wireinject
    // +build wireinject
    
    // wire.go
    package internal
    
    import "github.com/google/wire"
    
    func InitializeEvent() Event {
     wire.Build(NewEvent, NewGreeter, NewMessage)
     return Event{}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.进阶用法

    返回错误

    在Go中如果遇到错误,我们会在最后一个返回值返回error,wire同样也支持返回错误的情况,只需要在 injector的函数签名中加上error返回值即可

    调整provider的签名

    type Event struct {
     Greeter Greeter
    }
    
    //func NewEvent(g Greeter) Event {
    // return Event{Greeter: g}
    //}
    func NewEvent(g Greeter) (Event, error) {
     if time.Now().Unix()%2 == 0 {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
     }
    
     return Event{Greeter: g}, nil
    }
    
    func (e Event) Start() {
     msg := e.Greeter.Greet()
     fmt.Println(msg)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    调整injector的签名

    // wire.go
    func InitializeEvent() (Event, error) {
     panic(wire.Build(NewEvent, NewGreeter, NewMessage))
    }
    
    • 1
    • 2
    • 3
    • 4

    生成的代码如下所示,可以发现会像我们自己写代码一样判断一下 if err 然后返回

    // Code generated by Wire. DO NOT EDIT.
    
    //go:generate go run github.com/google/wire/cmd/wire
    //go:build !wireinject
    // +build !wireinject
    
    package main
    
    // Injectors from wire.go:
    
    func InitializeEvent() (Event, error) {
     message := NewMessage()
     greeter := NewGreeter(message)
     event, err := NewEvent(greeter)
     if err != nil {
      return Event{}, err
     }
     return event, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    main中的调用

    func main() {
        e, err := InitializeEvent()
        if err != nil {
            fmt.Printf("failed to create event: %s\n", err)
            os.Exit(2)
        }
        e.Start()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    传入参数

    如果要构建的目标组件需要外部的输入时,可以在定义provider 和 injector时同步加上输入

    调整provider的签名

    // provider.go
    type Message string
    
    func NewMessage(phrase string) Message {
     return Message(phrase)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    调整injector的签名

    // wire.go
    func InitializeEvent(phrase string) (Event, error) {
     wire.Build(NewEvent, NewGreeter, NewMessage)
    }
    
    • 1
    • 2
    • 3
    • 4

    不展示生成的代码了,main中的调用

    func main() {
     event, err := InitializeEvent("Hi there!")
     if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
     }
    
     event.Start()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ProviderSet

    有时候可能多个类型有相同的依赖,我们每次都将相同的构造器传给wire.Build()不仅繁琐,而且不易维护,一个依赖修改了,所有传入wire.Build()的地方都要修改。为此,wire提供了一个ProviderSet(构造器集合),可以将多个构造器打包成一个集合,后续只需要使用这个集合即可。

    如下,后续再调整NewGreeter和NewMessage,就可以统一改了

    // wire.go
    
    var wireSet = wire.NewSet(NewGreeter, NewMessage)
    
    func InitializeEvent(phrase string) (Event, error) {
     wire.Build(wireSet, NewEvent)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结构构造器

    对于Greeter,前文使用NewGreeter作为provider

    type Greeter struct {
     Message Message
    }
    
    func NewGreeter(m Message) Greeter {
     return Greeter{Message: m}
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果不显式实现NewGreeter,可以直接使用wire提供的结构构造器(Struct Provider)。结构构造器创建某个类型的结构,然后用参数或调用其它构造器填充它的字段

    结构构造器使用wire.Struct函数,第一个参数固定为new(结构名),后面可接任意多个参数,表示需要为该结构的哪些字段注入值

    我们也可以使用通配符*表示注入所有字段

    如下的例子代表Greeter需要注入Message字段,不用再单独实现NewGreeter了

    Greeter
    var wireSet = wire.NewSet(NewMessage, wire.Struct(new(Greeter), "Message"))
    
    func InitializeEvent(phrase string) (Event, error) {
     wire.Build(wireSet, NewEvent)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构字段作为构造器

    有时候我们编写一个构造器,只是简单的返回某个结构的一个字段,这时可以使用wire.FieldsOf简化操作。

    type Foo struct {
        S string
        N int
        F float64
    }
    
    func getS(foo Foo) string {
        // Bad! Use wire.FieldsOf instead.
        return foo.S
    }
    
    func provideFoo() Foo {
        return Foo{ S: "Hello, World!", N: 1, F: 3.14 }
    }
    
    func injectedMessage() string {
        wire.Build(
            provideFoo,
            getS)
        return ""
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    func injectedMessage() string {
        wire.Build(
            provideFoo,
            wire.FieldsOf(new(Foo), "S"))
        return ""
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    同样的,第一个参数为new(结构名),后面跟多个参数表示将哪些字段作为构造器,*表示全部。

    绑定值

    有时候,我们需要为某个类型绑定一个值,而不想依赖构造器每次都创建一个新的值。有些类型天生就是单例,例如配置,数据库对象(sql.DB)。这时我们可以使用wire.Value绑定值,使用wire.InterfaceValue绑定接口

    修改一下 Greeter 使他依赖一个int和io.Reader然后为它直接绑定 a=10 、io.Reader = os.Stdin

    // main.go
    var singletonMessage = NewMessage("Hello, world!")
    
    type Message string
    
    func NewMessage(phrase string) Message {
     return Message(phrase)
    }
    
    type Greeter struct {
     a int
     r io.Reader
    
     Message Message
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    var wireSet = wire.NewSet(
     wire.Struct(new(Greeter), "*"),
     wire.Value(10),
     wire.InterfaceValue(new(io.Reader), os.Stdin),
     wire.Value(singletonMessage),
    )
    
    func InitializeEvent(phrase string) (Event, error) {
     panic(wire.Build(wireSet, NewEvent))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    绑定接口
    使用 wire.Bind 将 Struct 和接口进行绑定,表示这个结构体实现了这个接口,wire.Bind 的使用方法就是 wire.Bind(new(接口), new(实现))

    func NewGreeter(m Message, phrase string) Greeter {
     return Greeter{Message: m}
    }
    
    func (g Greeter) Greet() Message {
     return g.Message
    }
    
    type IGreeter interface{
     Greet() Message
    }
    
    type Event struct {
     Greeter IGreeter
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    var wireSet = wire.NewSet(
     wire.Struct(new(Greeter), "*"),
     wire.Value(10),
     wire.InterfaceValue(new(io.Reader), os.Stdin),
     wire.Value(singletonMessage),
    )
    
    func InitializeEvent(phrase string) (Event, error) {
     panic(wire.Build(wireSet, NewEvent, wire.Bind(new(IGreeter), new(*Greeter))))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    总结

    本文介绍依赖注入框架wire的基础用法:

    • 实现各struct的工厂函数,wire称之为provider
    • 在wire.go中利用函数签名和函数体中调用wire.Build描述一个struct的所有依赖,此函数被称为 injector
    • 执行wire命令,会生成wire_gen.go 其中包含和injector签名相同的函数,函数的内容为构建的相关依赖并组合
    • 使用wire_gen.go中的函数创建我们的实例

    除了基础用法之外,还有很多高级用法,例如绑定接口、绑定值,来实现面向接口编程和单例模式,此部分可以参考官方文档

  • 相关阅读:
    【图像去噪】基于隐马尔可夫模型实现图像去噪处理附matlab代码
    PX4飞行测试
    tokenizers pre_tokenizers模块
    不要再抱怨项目资源不足了,这么办都能解决
    双vip的MySQL高可用集群
    【树莓派不吃灰系列】快速导航
    蚂蚁链发布全新Web3品牌ZAN,涉及RWA、合规等服务
    pytorch使用LSTMCell层定义LSTM网络结构
    redis 介绍
    深入理解MySQL索引:从原理到最佳实践
  • 原文地址:https://blog.csdn.net/weixin_50071922/article/details/133278161