golang中的依赖注入之wire的使用

作者:

文章目录
  • wire是 Google 开源的一个依赖注入工具。它是一个代码生成器,并不是一个框架。我们只需要在一个特殊的go文件中告诉wire类型之间的依赖关系,它会自动帮我们生成代码,帮助我们创建指定类型的对象,并组装它的依赖。
  • $ go install github.com/google/wire/cmd/wire@latest
  • 正式开始前需要先了解一下 wire 当中的两个概念:provider 和 injector
  • 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) } 设计一个程序,其中 Event依赖Greeter,Greeter依赖Message 如果我们手动构建Event则需要 func main() { message := NewMessage() greeter := NewGreeter(message) event := NewEvent(greeter) event.Start() }
  • 目录
    • Wire
    • 安装
    • 两个概念
      • Provider
      • Injector
    • 示例
      • 使用wire生成代码
      • 返回错误
      • 传入参数
      • ProviderSet
      • 结构构造器
      • 结构字段作为构造器
      • 绑定值
      • 绑定接口

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

    $ go install github.com/google/wire/cmd/wire@latest
    

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

    Provider 是一个普通的函数,这个函数会返回构建依赖关系所需的组件。如下所示,就是一个 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。

    // NewPostRepo 创建文章 Repo
    func NewPostRepo() IPostRepo {}
    

    不过需要注意的是在 wire 中不能存在两个 provider 返回相同的组件类型

    injector 也是一个普通函数,我们常常在 wire.go 文件中定义 injector 函数签名,然后通过 wire 命令自动生成一个完整的函数

    //+build wireinject
    
    func GetBlogService() *Blog {
        panic(wire.Build(NewBlogService, NewPostUsecase, NewPostRepo))
    }
    

    第一行的 //+build wireinject 注释确保了这个文件在我们正常编译的时候不会被引用,而 wire . 生成的文件 wire_gen.go 会包含 //+build !wireinject 注释,正常编译的时候,不指定 tag 的情况下会引用这个文件

    wire.Buildinjector 函数中使用,用于表名这个 injector 由哪些 provider 提供依赖, injector 函数本身只是一个函数签名,所以我们直接在函数中 panic 实际生成代码的时候并不会直接调用 panic

    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)
    }
    

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

    如果我们手动构建Event则需要

    func main() {
        message := NewMessage()
        greeter := NewGreeter(message)
        event := NewEvent(greeter)
    
        event.Start()
    }
    

    对于上面的NewEventNewGreeterNewMessage都是provider

    我们只要再创建一个独立的文件 wrie.go,并在里面创建一个Injector即可

    //go:build wireinject
    // +build wireinject
    
    package main
    
    import "github.com/google/wire"
    
    func InitializeEvent() Event {
    	panic(wire.Build(NewEvent, NewGreeter, NewMessage))
    }
    

    在执行完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 main
    
    // Injectors from wire.go:
    
    func InitializeEvent() Event {
    	message := NewMessage()
    	greeter := NewGreeter(message)
    	event := NewEvent(greeter)
    	return event
    }
    

    此时我们就可以直接在main里这样用

    func main() {
    	event := InitializeEvent()
    	event.Start()
    }
    

    在 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)
    }
    

    调整Injector的签名

    func InitializeEvent() (Event, error) {
    	panic(wire.Build(NewEvent, NewGreeter, NewMessage))
    }
    

    生成的代码如下所示,可以发现会像我们自己写代码一样判断一下 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
    }
    

    main中的调用

    func main() {
        e, err := InitializeEvent()
        if err != nil {
            fmt.Printf("failed to create event: %sn", err)
            os.Exit(2)
        }
        e.Start()
    }
    

    调整Provider的签名

    type Message string
    
    //func NewMessage() Message {
    //	return Message("Hi there!")
    //}
    func NewMessage(phrase string) Message {
    	return Message(phrase)
    }
    

    调整Injector的签名

    func InitializeEvent(phrase string) (Event, error) {
    	panic(wire.Build(NewEvent, NewGreeter, NewMessage))
    }
    

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

    func main() {
    	event, err := InitializeEvent("Hi there!")
    	if err != nil {
    		fmt.Printf("failed to create event: %sn", err)
    		os.Exit(2)
    	}
    
    	event.Start()
    }
    

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

    如下,后续再调整NewGreeterNewMessage,就可以统一改了

    package main
    
    import "github.com/google/wire"
    
    var wireSet = wire.NewSet(NewGreeter, NewMessage)
    
    func InitializeEvent(phrase string) (Event, error) {
    	panic(wire.Build(wireSet, NewEvent))
    }
    
    func InitializeEvent2(phrase string) (Event, error) {
    	panic(wire.Build(wireSet, NewEvent))
    }
    

    对于Greeter前面使用NewGreeter作为Provider

    type Greeter struct {
    	Message Message
    }
    
    func NewGreeter(m Message, phrase string) Greeter {
    	return Greeter{Message: m}
    }
    

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

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

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

    var wireSet = wire.NewSet(NewMessage, wire.Struct(new(Greeter), "Message"))
    
    func InitializeEvent(phrase string) (Event, error) {
    	panic(wire.Build(wireSet, NewEvent))
    }
    

    有时候我们编写一个构造器,只是简单的返回某个结构的一个字段,这时可以使用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 ""
    }
    
    func injectedMessage() string {
        wire.Build(
            provideFoo,
            wire.FieldsOf(new(Foo), "S"))
        return ""
    }
    

    同样的,第一个参数为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
    }
    
    func NewGreeter(m Message, phrase string) Greeter {
    	return Greeter{Message: m}
    }
    
    func (g Greeter) Greet() Message {
    	return g.Message
    }
    
    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.BindStruct 和接口进行绑定,表示这个结构体实现了这个接口,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
    }
    
    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))))
    }
    

    到此这篇关于golang中的依赖注入之wire的文章就介绍到这了,更多相关golang 依赖注入wire内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • go依赖注入管理工具wire的使用方法
    • 深入浅出go依赖注入工具Wire的使用
    • 如何组织Go代码目录结构依赖注入wire使用解析
    • Go依赖注入工具wire的具体使用
    • Golang 官方依赖注入工具wire示例详解
    • Go语言官方依赖注入工具Wire的使用教程
    • Go依赖注入DI工具wire使用详解(golang常用库包)

    站内搜索