前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 官方依赖注入工具wire

Go 官方依赖注入工具wire

作者头像
孤烟
发布2023-01-06 09:26:40
1.6K0
发布2023-01-06 09:26:40
举报
文章被收录于专栏:golang开发笔记golang开发笔记

wire是Go官方推出的一款类似于Spring依赖注入工具。有别于以往的依赖注入工具facebookgo/inject、uber-go/dig等,采用反射实现。wire采用通过代码描述对象之间的依赖关系,然后自动生成代码在编译期实现依赖注入的工具 源码:https://github.com/google/wire

什么是依赖注入

说到依赖注入(Dependency Injection,缩写DI),不得不提控制反转(Inversion of Control,缩写为IoC)。IoC是一种设计思想,核心作用是降低代码耦合度。 传统系统应用是在类内部主动引用对象,从而导致类与类之间高度耦合,不利于维护,而有了IoC容器后,把创建和查找对象工作交给容器,由容器动态的将某个依赖关系注入对象中,控制权由调用者应用代码转移到IoC容器,控制权发生了反转,从而实现对象间解耦。依赖注入是实现IoC解决依赖问题的设计模式。

举例

代码语言:javascript
复制
type TestA struct {
    B *TestB//依赖

}
type TestB struct {

}

func NewA() *TestA {
    return &TestA{
        B:new(TestB),
    }
}

上面代码,TestA依赖TestB,这样以后加入需要对TestB修改时,还需要对TestA做修改。这样做有以下几个问题: 代码耦合性高不利于维护这种依赖关系 不利于功能复用,TestA无法复用示例好的TestB 不方便单元测试

为此我们可以对代码以依赖注入方式修改

代码语言:javascript
复制
type TestA struct {
    B *TestB

}
type TestB struct {

}

func NewA(b *TestB) *TestA {
    return &TestA{
        B:b,
    }
}

这样,我们将初始化后的TestB注入到NewA中了,解耦了部分依赖。 以上的依赖注入方式,在代码少,系统不复杂时实现起来没问题,当系统庞大到一定程序时就力不从心了。怎么解决呢?这里就需要着重介绍的wire依赖注入工具了。

wire 使用方法

安装
代码语言:javascript
复制
go install github.com/google/wire/cmd/wire@latest

并确保将GOPATH/bin其添加到您的PATH.

基本

Wire 有两个核心概念:providers 和 injectors。 providers 示例 在foobarbaz目录下创建文件foobarbaz.go,内容如下

代码语言:javascript
复制
package foobarbaz

import (
    "context"
    "errors"
)

type Foo struct {
    X int
}

// ProvideFoo returns a Foo.
func ProvideFoo() Foo {
    return Foo{X: 42}
}

type Bar struct {
    X int
}

// ProvideBar returns a Bar: a negative Foo.
func ProvideBar(foo Foo) Bar {
    return Bar{X: -foo.X}
}

type Baz struct {
    X int
}

// ProvideBaz returns a value if Bar is not zero.
func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
    if bar.X == 0 {
        return Baz{}, errors.New("cannot provide baz when bar is zero")
    }
    return Baz{X: bar.X}, nil
}

injectors:创建wire.go文件(文件名可以不是wire,但一般是这个) 示例

代码语言:javascript
复制
// +build wireinject
// The build tag makes sure the stub is not built in the final build.

package main

import (
    "context"
    "demo/foobarbaz"
    "github.com/google/wire"
)

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    wire.Build(foobarbaz.ProvideFoo,foobarbaz.ProvideBar,foobarbaz.ProvideBaz)
    return foobarbaz.Baz{}, nil
}

注意:需要在文件头部增加构建约束://+build wireinject 执行wire自动生成依赖代码,可以直接用wire或者用wire gen wire.go来生成wire_gen.go文件。 生成代码如下

代码语言:javascript
复制
// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

import (
    "context"
    "demo/foobarbaz"
)

// Injectors from wire.go:

func initializeBaz(ctx context.Context) (foobarbaz.Baz, error) {
    foo := foobarbaz.ProvideFoo()
    bar := foobarbaz.ProvideBar(foo)
    baz, err := foobarbaz.ProvideBaz(ctx, bar)
    if err != nil {
        return foobarbaz.Baz{}, err
    }
    return baz, nil
}

通过以上代码,可以看到自动生成的代码包含了error处理,跟手动写的代码几乎一样。 wire.go文件头部//+build wireinject,+build 其实是 Go 语言的一个特性,确保在go build编译时不处理此文件。 wire_gen.go文件头部// +build !wireinject,其中的!wireinject是来告诉wire命令不处理此文件。

高级特性

NewSet

NewSet 一般应用在初始化对象比较多的情况下,减少 Injector 里面的信息。

代码语言:javascript
复制
var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)

wire.Build(SuperSet)
Struct

除了函数可以作为Provider外,stuct也可以作为Provider。对于生成的结构类型S,wire.Struct同时提供S和*S。 示例

代码语言:javascript
复制
type Foo int
type Bar int

func ProvideFoo() Foo {/* ... */}

func ProvideBar() Bar {/* ... */}

type FooBar struct {
    MyFoo Foo
    MyBar Bar
}

var Set = wire.NewSet(
    ProvideFoo,
    ProvideBar,
    wire.Struct(new(FooBar), "MyFoo", "MyBar"))

生成的注入器FooBar如下所示:

代码语言:javascript
复制
func injectFooBar() FooBar {
    foo := ProvideFoo()
    bar := ProvideBar()
    fooBar := FooBar{
        MyFoo: foo,
        MyBar: bar,
    }
    return fooBar
}

第一个参数wire.Struct是指向所需结构类型的指针,后续参数是要注入的字段的名称。一个特殊的字符串""可以用作告诉注入器注入所有字段的快捷方式。所以wire.Struct(new(FooBar), "")产生与上面相同的结果。 对于上面的示例,您可以"MyFoo"通过更改为 Set来指定仅注入:

代码语言:javascript
复制
var Set = wire.NewSet(
    ProvideFoo,
    wire.Struct(new(FooBar), "MyFoo"))

那么生成的注入器FooBar将如下所示:

代码语言:javascript
复制
func injectFooBar() FooBar {
    foo := ProvideFoo()
    fooBar := FooBar{
        MyFoo: foo,
    }
    return fooBar
}

如果注入器返回 a*FooBar而不是 a FooBar,生成的注入器将如下所示:

代码语言:javascript
复制
func injectFooBar() *FooBar {
    foo := ProvideFoo()
    fooBar := &FooBar{
        MyFoo: foo,
    }
    return fooBar
}

有时防止某些字段被注入器填充是很有用的,尤其是在传递*给wire.Struct. 您可以标记一个字段, wire:"-"让 Wire 忽略这些字段。例如:

代码语言:javascript
复制
type Foo struct {
    mu sync.Mutex `wire:"-"`
    Bar Bar
}

当您使用 提供Foo类型时wire.Struct(new(Foo), "*"),Wire 将自动省略该mu字段。此外,像在wire.Struct(new(Foo), "mu").

Bind

Bind 函数的作用是为了让接口类型的依赖参与 Wire 的构建。Wire 的构建依靠参数类型,接口类型是不支持的。Bind 函数通过将接口类型和实现类型绑定,来达到依赖注入的目的。

代码语言:javascript
复制
type Foo struct {
    X int
}

func injectFoo() Foo {
    wire.Build(wire.Value(Foo{X: 42}))
    return Foo{}
}
CleanUp

如果提供者创建了一个需要清理的值(例如关闭文件),那么它可以返回一个闭包来清理资源。如果稍后在注入器实现中调用的提供者返回错误,注入器将使用它向调用者返回聚合清理函数或清理资源。

代码语言:javascript
复制
func provideFile(log Logger, path Path) (*os.File, func(), error) {
    f, err := os.Open(string(path))
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        if err := f.Close(); err != nil {
            log.Log(err)
        }
    }
    return f, cleanup, nil
}

清理函数保证在任何提供者输入的清理函数之前被调用,并且必须具有签名func()。

备用注入器语法

如果你厌倦了return foobarbaz.Foo{}, nil在注入器函数声明的末尾写,你可以用一个更简洁的方式来写 panic:

代码语言:javascript
复制
func injectFoo() Foo {
    panic(wire.Build(/* ... */))
}

注意问题

果我的依赖关系图有两个相同类型的依赖关系怎么办?

Wire 不允许在提供给 的提供者的传递闭包中存在一个类型的多个提供者wire.Build,因为这通常是一个错误。对于需要相同类型的多个依赖项的合法情况,您需要发明一种新类型来调用此其他依赖项。

代码语言:javascript
复制
type Foo struct { /* ... */ }
type Bar struct { /* ... */ }

func newFoo1() *Foo { /* ... */ }
func newFoo2() *Foo { /* ... */ }

解决办法

代码语言:javascript
复制
type Foo1 Foo
type Foo2 Foo
func newFoo1() *Foo1 { /* ... */ }
func newFoo2() *Foo2 { /* ... */ }

总结

wire通过程序自动生成跟手动写一样代码,没有使用低效的反射,效率高。 如果不小心忘记了某个provider, wire 会报出具体的错误, 帮忙开发者迅速定位问题。

links

https://github.com/google/wire https://juejin.cn/post/714801...

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是依赖注入
  • wire 使用方法
    • 安装
      • 基本
      • 高级特性
        • NewSet
          • Struct
            • Bind
              • CleanUp
                • 备用注入器语法
                • 注意问题
                  • 果我的依赖关系图有两个相同类型的依赖关系怎么办?
                  • 总结
                  • links
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档