为何而来 發表於 2024-4-7 16:12:00

Go 项目依赖注入wire工具最佳实践介绍与使用

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>一、引入</li><li>二、控制反转与依赖注入</li><li>三、为什么需要依赖注入工具<ul><li>3.1 示例</li><li>3.2 依赖注入写法与非依赖注入写法</li></ul></li><li>四、wire 工具介绍与安装<ul><li>4.1 wire 基本介绍</li><li>4.2 安装</li></ul></li><li>五、Wire 的基本使用<ul><li>5.1 前置代码准备</li><li>5.2 使用 Wire 工具生成代码</li></ul></li><li>六、Wire 核心技术<ul><li>5.1 抽象语法树分析</li><li>5.2 模板编程</li></ul></li><li>七、Wire 的核心概念<ul><li>7.1 两个核心概念</li><li>7.2 Wire 提供者(providers)</li><li>7.3 Wire 注入器(injectors)</li></ul></li><li>八、Wire 的高级用法<ul><li>8.1 绑定接口</li><li>8.2 结构体提供者(Struct Providers)</li><li>8.3 绑定值</li><li>8.4 使用结构体字段作为提供者(providers)</li><li>8.5 清理函数</li><li>8.6 备用注入器语法</li></ul></li><li>九、参考文档</li></ul></div><p></p>
<h2 id="一引入">一、引入</h2>
<p>在Go语言的项目开发中,为了提高代码的可测试性和可维护性,我们通常会采用依赖注入(<code>Dependency Injection</code>,简称DI)的设计模式。依赖注入可以让高层模块不依赖底层模块的具体实现,而是通过抽象来互相依赖,从而使得模块之间的耦合度降低,系统的灵活性和可扩展性增强。</p>
<h2 id="二控制反转与依赖注入">二、控制反转与依赖注入</h2>
<p>控制反转(<code>Inversion of Control</code>,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(<code>Dependency Injection</code>,简称DI)。依赖注入是生成灵活和松散耦合代码的标准技术,通过明确地向组件提供它们所需要的所有依赖关系。在 Go 中通常采用将依赖项作为参数传递给构造函数的形式:</p>
<p>构造函数<code>NewUserRepository</code>在创建<code>UserRepository</code>时需要从外部将依赖项<code>db</code>作为参数传入,我们在<code>UserRepository</code>中无需关注<code>db</code>的创建逻辑,实现了代码解耦。</p>
<pre><code class="language-go">// NewUserRepository 创建BookRepo的构造函数
func NewUserRepository(db *gorm.DB) *UserRepository {
        return &amp;UserRepository{db: db}
}

</code></pre>
<p>区别于控制反转,如果在<code>NewUserRepository</code>函数中自行创建相关依赖,这将导致代码高度耦合并且难以维护和调试。</p>
<pre><code class="language-go">// NewUserRepository 创建UserRepository的构造函数
func NewUserRepository() *UserRepository {
db, _ := gorm.Open(sqlite.Open("gorm.db"), &amp;gorm.Config{})
        return &amp;UserRepository{db: db}
}
</code></pre>
<h2 id="三为什么需要依赖注入工具">三、为什么需要依赖注入工具</h2>
<h3 id="31-示例">3.1 示例</h3>
<p>如果上面示例代码不够清晰的话,我们来看这两段代码:</p>
<pre><code class="language-go">// NewUserRepositoryV1非依赖注入的写法
func NewUserRepositoryV1(dbCfg DBConfig, c CacheConfig)*UserRepository{
    db, err := gorm.Open(mysql.Open(dbcfg.DSN))
    if err != nil {
      panic(err)
    }
    ud = dao.NewUserDAO(db)
    uc = cache.NewUserCache(redis.NewClient(&amp;redis.Options{
            Addr: c.Addr,
    }))
    return &amp;UserRepository{
      dao: ud,
      cache: uc,
    }
}

// NewUserRepository 依赖注入的写法
func NewUserRepository(d *dao.UserDAO, c *cache.UserCache)*UserRepository{
    return &amp;UserRepository{
      dao: d,
      cache: c,
    }
}

</code></pre>
<p>可以清楚地看到,这两段代码展示了在Go语言中实现依赖注入的两种不同方式。<br>
第一段代码 <code>NewUserRepositoryV1</code> 是非依赖注入的写法。在这个函数中,<code>UserRepository</code> 的依赖(<code>db</code> 和 <code>cache</code>)是在函数内部创建的。这种方式的问题在于,它违反了单一职责原则,因为 <code>NewUserRepositoryV1</code> 不仅负责创建 <code>UserRepository</code> 实例,还负责创建其依赖的数据库和缓存客户端。这样做会导致代码耦合度较高,难以测试和维护。<br>
第二段代码 <code>NewUserRepository</code> 是依赖注入的写法。这个函数接受 <code>UserRepository</code> 的依赖(<code>*dao.UserDAO</code> 和 <code>*cache.UserCache</code>)作为参数,而不是在函数内部创建它们。这种方式使得 <code>UserRepository</code> 的创建与它的依赖解耦,更容易测试,因为你可以轻松地为 <code>UserRepository</code> 提供模拟的依赖项。此外,这种写法也更符合依赖注入的原则,因为它将控制反转给了调用者,由调用者来决定 <code>UserRepository</code> 实例化时使用哪些依赖项。</p>
<h3 id="32-依赖注入写法与非依赖注入写法">3.2 依赖注入写法与非依赖注入写法</h3>
<p><strong>依赖注入写法</strong>:不关心依赖是如何构造的。</p>
<p><strong>非依赖注入写法</strong>:必须自己初始化依赖,比如说 <code>Repository</code> 需要知道如何初始化 <code>DAO</code> 和 <code>Cache</code>。由此带来的缺点是:</p>
<ul>
<li>深度耦合依赖的初始化过程。</li>
<li>往往需要定义额外的 <code>Config</code> 类型来传递依赖所需的配置信息。</li>
<li>一旦依赖增加新的配置,或者更改了初始化过程,都要跟着修改。</li>
<li>缺乏扩展性。</li>
<li>测试不友好。</li>
<li>难以复用公共组件,例如 DB 或 Redis 之类的客户端。</li>
</ul>
<h2 id="四wire-工具介绍与安装">四、wire 工具介绍与安装</h2>
<h3 id="41-wire-基本介绍">4.1 wire 基本介绍</h3>
<ul>
<li>
<p>Wire 是一个的 Google 开源专为依赖注入(<code>Dependency Injection</code>)设计的代码生成工具,通过自动生成代码的方式在<strong>初始编译过程中</strong>完成依赖注入。它可以自动生成用于化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。</p>
</li>
<li>
<p><code>Wire</code> 分成两部分,一个是在项目中使用的依赖, 一个是命令行工具。</p>
</li>
</ul>
<h3 id="42-安装">4.2 安装</h3>
<pre><code class="language-go">go install github.com/google/wire/cmd/wire@latest
</code></pre>
<h2 id="五wire-的基本使用">五、Wire 的基本使用</h2>
<h3 id="51-前置代码准备">5.1 前置代码准备</h3>
<p>目录结构如下:</p>
<pre><code class="language-go">wire
├── db.go                        # 数据库相关代码
├── go.mod                         # Go模块依赖配置文件
├── go.sum                         # Go模块依赖校验文件
├── main.go                        # 程序入口文件
├── repository                     # 存放数据访问层代码的目录
│   ├── dao                        # 数据访问对象(DAO)目录
│   │   └── user.go                # 用户相关的DAO实现
│   └── user.go                  # 用户仓库实现
├── wire.go                        # Wire依赖注入配置文件
</code></pre>
<p><code>repository/dao/user.go</code>文件:</p>
<pre><code class="language-go">// repository/dao/user.go
package dao

import "gorm.io/gorm"

type UserDAO struct {
        db *gorm.DB
}

func NewUserDAO(db *gorm.DB) *UserDAO {
        return &amp;UserDAO{
                db: db,
        }
}
</code></pre>
<p><code>repository/user.go</code> 文件:</p>
<pre><code class="language-go">// repository/user.go
package repository

import "wire/repository/dao"

type UserRepository struct {
        dao *dao.UserDAO
}

func NewUserRepository(dao *dao.UserDAO) *UserRepository {
        return &amp;UserRepository{
                dao: dao,
        }
}
</code></pre>
<p><code>db.go</code> 文件:</p>
<pre><code class="language-go">// db.go
package wire

import (
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
)

func InitDB() *gorm.DB {
        db, err := gorm.Open(mysql.Open("dsn"))
        if err != nil {
                panic(err)
        }
        return db
}
</code></pre>
<p><code>main.go</code> 文件:</p>
<pre><code class="language-go">package wire

import (
        "fmt"
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
        "wire/repository"
        "wire/repository/dao"
)

func main() {
        // 非依赖注入
        db, err := gorm.Open(mysql.Open("dsn"))
        if err != nil {
                panic(err)
        }
        ud := dao.NewUserDAO(db)
        repo := repository.NewUserRepository(ud)
        fmt.Println(repo)
}
</code></pre>
<h3 id="52-使用-wire-工具生成代码">5.2 使用 Wire 工具生成代码</h3>
<p>现在我们已经有了基本的代码结构,接下来我们将使用 <code>wire</code> 工具来生成依赖注入的代码。</p>
<p>首先,确保你已经安装了 <code>wire</code> 工具。如果没有安装,可以使用以下命令安装:</p>
<pre><code class="language-go">go get github.com/google/wire/cmd/wire
</code></pre>
<p>接下来,我们需要创建一个 <code>wire</code> 的配置文件,通常命名为 <code>wire.go</code>。在这个文件中,我们将使用 <code>wire</code> 的语法来指定如何构建 <code>UserRepository</code> 实例。</p>
<p><code>wire.go</code> 文件:</p>
<pre><code class="language-go">//go:build wireinject

// 让 wire 来注入这里的代码
package wire

import (
        "github.com/google/wire"
        "wire/repository"
        "wire/repository/dao"
)

func InitRepository() *repository.UserRepository {
        // 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
        // 这个方法里面传入各个组件的初始化方法
        wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
        return new(repository.UserRepository)
}

</code></pre>
<p>这段代码是使用 <code>wire</code> 工具进行依赖注入的配置文件。在这个文件中,我们定义了一个函数 <code>InitRepository</code>,这个函数的目的是为了生成一个 <code>*repository.UserRepository</code> 的实例。但是,这个函数本身并不包含具体的实现代码,而是依赖于 <code>wire</code> 工具来注入依赖。<br>
让我们逐步解释这段代码:</p>
<ol>
<li>
<p><strong>构建约束指令</strong>:</p>
<pre><code class="language-go">//go:build wireinject
</code></pre>
<p>这行注释是一个构建约束,它告诉 <code>go build</code> 只有在满足条件 <code>wireinject</code> 的情况下才应该构建这个文件。<code>wireinject</code> 是一个特殊的标签,用于指示 <code>wire</code> 工具处理这个文件。</p>
</li>
<li>
<p><strong>导入包</strong>:</p>
<pre><code class="language-go">import (
    "github.com/google/wire"
    "wire/repository"
    "wire/repository/dao"
)
</code></pre>
<p>这部分导入了必要的包,包括 <code>wire</code> 工具库,以及项目中的 <code>repository</code> 和 <code>dao</code> 包,这些包包含了我们需要注入的依赖。</p>
</li>
<li>
<p><strong>InitRepository 函数</strong>:</p>
<pre><code class="language-go">func InitRepository() *repository.UserRepository {
    // 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
    // 这个方法里面传入各个组件的初始化方法
    wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
    return new(repository.UserRepository)
}
</code></pre>
<p>这个函数是 <code>wire</code> 注入的目标。它声明了一个返回 <code>*repository.UserRepository</code> 的函数,但是函数体内部没有具体的实现代码。<strong><code>wire.Build</code> 函数调用是关键, 主要是连接或绑定我们之前定义的所有初始化函数。当我们运行 <code>wire</code> 工具来生成代码时,它就会根据这些依赖关系来自动创建和注入所需的实例。</strong>,这些函数按照依赖关系被调用,以正确地构造和注入 <code>UserRepository</code> 实例所需的依赖。</p>
<ul>
<li><code>InitDB</code> 是初始化数据库连接的函数。</li>
<li><code>repository.NewUserRepository</code> 是创建 <code>UserRepository</code> 实例的函数。</li>
<li><code>dao.NewUserDAO</code> 是创建 <code>UserDAO</code> 实例的函数。<br>
<code>wire</code> 工具会自动生成这些函数调用的代码,并确保依赖关系得到满足。</li>
</ul>
</li>
<li>
<p><strong>返回语句</strong>:</p>
<pre><code class="language-go">return new(repository.UserRepository)
</code></pre>
<p>这个返回语句是必须的,尽管它实际上并不会被执行。<code>wire</code> 工具会生成一个替换这个函数体的代码,其中包括所有必要的依赖注入逻辑。<br>
在编写完 <code>wire.go</code> 文件后,你需要运行 <code>wire</code> 命令来生成实际的依赖注入代码。生成的代码将被放在一个名为 <code>wire_gen.go</code> 的文件中,这个文件应该被提交到你的版本控制系统中。</p>
</li>
</ol>
<p>现在,我们可以运行 <code>wire</code> 命令来生成依赖注入的代码:</p>
<pre><code class="language-go">wire
</code></pre>
<p>这个命令会扫描 <code>wire.go</code> 文件,并生成一个新的 Go 文件 <code>wire_gen.go</code>,其中包含了 <code>InitializeUserRepository</code> 函数的实现,这个函数会创建并返回一个 <code>UserRepository</code> 实例,其依赖项已经自动注入。</p>
<p><img src="https://img2024.cnblogs.com/other/2153830/202404/2153830-20240407161201530-1086805979.png" alt="" loading="lazy"></p>
<p>生成 <code>wire_gen.go</code> 文件,内容如下所示:</p>
<pre><code class="language-go">// Code generated by Wire. DO NOT EDIT.

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

package wire

import (
        "wire/repository"
        "wire/repository/dao"
)

// Injectors from wire.go:

func InitRepository() *repository.UserRepository {
        db := InitDB()
        userDAO := dao.NewUserDAO(db)
        userRepository := repository.NewUserRepository(userDAO)
        return userRepository
}
</code></pre>
<p>最后,我们需要修改 <code>main.go</code> 文件,使用 <code>wire</code> 生成的代码来获取 <code>UserRepository</code> 实例:</p>
<pre><code class="language-go">package wire

func main() {
        InitRepository()
}
</code></pre>
<p>现在,当我们运行 <code>main.go</code> 时,它将使用 <code>wire</code> 工具生成的代码来初始化 <code>UserRepository</code>,包括其依赖的 <code>UserDAO</code> 和数据库连接。这样,我们就实现了依赖注入,并且代码更加简洁、易于维护。</p>
<h2 id="六wire-核心技术">六、Wire 核心技术</h2>
<h3 id="51-抽象语法树分析">5.1 抽象语法树分析</h3>
<p><code>wire</code> 工具的工作原理是基于对Go代码的抽象语法树(Abstract Syntax Tree,简称AST)的分析。AST是源代码的抽象语法结构的树状表示,它以树的形式表现编程语言的语法结构。<code>wire</code> 工具通过分析AST来理解代码中的依赖关系。<br>
在Go中,<code>go/ast</code> 包提供了解析Go源文件并构建AST的功能。<code>wire</code> 工具利用这个包来遍历和分析项目的Go代码,识别出所有的依赖项,并构建出依赖关系图。这个依赖关系图随后被用来生成注入依赖的代码。</p>
<h3 id="52-模板编程">5.2 模板编程</h3>
<p><code>wire</code> 工具生成代码的过程也涉及到模板编程。模板编程是一种编程范式,它允许开发者定义一个模板,然后使用具体的数据来填充这个模板,生成最终的代码或文本。<br>
在<code>wire</code>中,虽然不直接使用Go语言的模板引擎(如<code>text/template</code>或<code>html/template</code>),但它的工作原理与模板编程类似。<code>wire</code>定义了一套自己的语法来描述依赖关系,然后根据这些描述生成具体的Go代码。<br>
<code>wire</code>的语法主要包括以下几个部分:</p>
<ul>
<li><code>wire.NewSet</code>:定义一组相关的依赖,通常包括一个或多个构造函数。</li>
<li><code>wire.Build</code>:指定生成代码时应该使用哪些依赖集合。</li>
<li><code>bind</code> 函数:用于绑定接口和实现,告诉<code>wire</code>如何创建接口的实例。<br>
<code>wire</code>工具通过这些语法来构建一个依赖图,然后根据这个图生成一个函数,该函数负责创建并返回所有必要的组件实例,同时处理它们之间的依赖关系。<br>
通过结合抽象语法树分析和模板编程,<code>wire</code> 工具能够提供一种声明式的依赖注入方法,让开发者能够专注于定义依赖关系,而不是手动编写依赖注入的代码。这不仅减少了重复劳动,还提高了代码的可维护性和降低了出错的可能性。</li>
</ul>
<h2 id="七wire-的核心概念">七、Wire 的核心概念</h2>
<h3 id="71-两个核心概念">7.1 两个核心概念</h3>
<p>在 <code>wire</code> 中,有两个核心概念:提供者(providers)和注入器(injectors)。</p>
<h3 id="72-wire-提供者providers">7.2 Wire 提供者(providers)</h3>
<p><strong>提供者</strong> 是一个普通有返回值的 Go 函数,它负责创建一个对象或者提供依赖。在 <code>wire</code> 的上下文中,提供者可以是任何返回一个或多个值的函数。这些返回值将成为注入器函数的参数。提供者函数通常负责初始化组件,比如数据库连接、服务实例等。并且提供者的返回值不仅限于一个,如果有需要的话,可以额外添加一个 <code>error</code> 的返回值。<br>
例如,一个提供者函数可能会创建并返回一个数据库连接:</p>
<pre><code class="language-go">func NewDBConnection(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(mysql.Open(dsn))
    if err != nil {
      return nil, err
    }
    return db, nil
}
</code></pre>
<p>提供者函数可以分组为提供者函数集(<strong>provider set</strong>)。使用<code>wire.NewSet</code> 函数可以将多个提供者函数添加到一个集合中。举个例子,例如将 <code>user</code> 相关的 <code>handler</code> 和 <code>service</code> 进行组合:</p>
<pre><code class="language-go">package web

var UserSet = wire.NewSet(NewUserHandler, service.NewUserService)
</code></pre>
<p>使用 <code>wire.NewSet</code> 函数将提供者进行分组,该函数返回一个 <code>ProviderSet</code> 结构体。不仅如此,<code>wire.NewSet</code> 还能对多个 <code>ProviderSet</code> 进行分组 <code>wire.NewSet(UserSet, XxxSet) </code>。</p>
<pre><code class="language-go">package demo

import (
    // ...
    "example.com/some/other/pkg"
)

// ...

var MegaSet = wire.NewSet(UserSet, pkg.OtherSet)
</code></pre>
<h3 id="73-wire-注入器injectors">7.3 Wire 注入器(injectors)</h3>
<p>注入器(<code>injectors</code>)的作用是将所有的提供者(<code>providers</code>)连接起来,要声明一个注入器函数只需要在函数体中调用<code>wire.Build()</code>。这个函数的返回值也无关紧要,只要它们的类型正确即可。这些值在生成的代码中将被忽略。回顾一下我们之前的代码:</p>
<pre><code class="language-go">//go:build wireinject

// 让 wire 来注入这里的代码
package wire

import (
        "github.com/google/wire"
        "wire/repository"
        "wire/repository/dao"
)

func InitRepository() *repository.UserRepository {
        // 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
        // 这个方法里面传入各个组件的初始化方法
        wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
        return new(repository.UserRepository)
}
</code></pre>
<p>在这个例子中,<code>InitRepository</code> 是一个注入器,它依赖 <code>InitDB</code> 和 <code>repository.NewUserRepository</code> 这两个提供者。</p>
<p>与提供者一样,注入器也可以输入参数(然后将其发送给提供者),并且可以返回错误。<code>wire.Build</code>的参数和<code>wire.NewSet</code>一样:都是提供者集合。这些就在该注入器的代码生成期间使用的提供者集。</p>
<h2 id="八wire-的高级用法">八、Wire 的高级用法</h2>
<h3 id="81-绑定接口">8.1 绑定接口</h3>
<p>依赖项注入通常用于绑定接口的具体实现。<code>wire</code>通过类型标识将输入与输出匹配,因此倾向于创建一个返回接口类型的提供者。然而,这也不是习惯写法,因为Go的最佳实践是返回具体类型。你可以在提供者集中声明接口绑定.</p>
<p>我们对之前的代码进行改造:</p>
<p>首先,我们在<code>UserRepository</code>接口中定义一些方法。例如,我们可以定义一个<code>GetUser</code>方法,该方法接收一个用户ID,并返回相应的用户。 在<code>repository/user.go</code>文件中:</p>
<pre><code class="language-go">package repository

import (
    "wire/repository/dao"
    "gorm.io/gorm"
)

type UserRepository interface {
    GetUser(id uint) (*User, error)
}

type UserRepositoryImpl struct {
    dao *dao.UserDAO
}

func (r *UserRepositoryImpl) GetUser(id uint) (*User, error) {
    return r.dao.GetUser(id)
}

func NewUserRepository(dao *dao.UserDAO) UserRepository {
    return &amp;UserRepositoryImpl{
      dao: dao,
    }
}
</code></pre>
<p>然后,我们在<code>UserDAO</code>中实现这个<code>GetUser</code>方法。在<code>repository/dao/user.go</code>文件中:</p>
<pre><code class="language-go">package dao

import (
    "gorm.io/gorm"
)

type User struct {
    ID uint
    // other fields...
}

type UserDAO struct {
    db *gorm.DB
}

func (dao *UserDAO) GetUser(id uint) (*User, error) {
    var user User
    result := dao.db.First(&amp;user, id)
    if result.Error != nil {
      return nil, result.Error
    }
    return &amp;user, nil
}

func NewUserDAO(db *gorm.DB) *UserDAO {
    return &amp;UserDAO{
      db: db,
    }
}
</code></pre>
<p>最后,我们需要更新<code>wire.go</code>文件中的<code>InitRepository</code>函数,以返回<code>UserRepository</code>接口,而不是具体的实现。 在<code>wire.go</code>文件中:</p>
<pre><code class="language-go">//go:build wireinject

package wire

import (
    "github.com/google/wire"
    "wire/repository"
    "wire/repository/dao"
)

func InitRepository() repository.UserRepository {
    wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
    return &amp;repository.UserRepositoryImpl{}
}
</code></pre>
<p>使用 <code>wire.Bind</code> 来建立接口类型和具体的实现类型之间的绑定关系,这样 <code>Wire</code> 工具就可以根据这个绑定关系进行类型匹配并生成代码。</p>
<p><code>wire.Bind</code> 函数的第一个参数是指向所需接口类型值的指针,第二个实参是指向实现该接口的类型值的指针。</p>
<h3 id="82-结构体提供者struct-providers">8.2 结构体提供者(Struct Providers)</h3>
<p><code>Wire</code> 库有一个函数是 <code>wire.Struct</code>,它能根据现有的类型进行构造结构体,我们来看看下面的例子:</p>
<pre><code class="language-go">package main

import "github.com/google/wire"

type Name string

func NewName() Name {
        return "小米SU7"
}

type PublicAccount string

func NewPublicAccount() PublicAccount {
        return "新一代车神"
}

type User struct {
        MyName          Name
        MyPublicAccount PublicAccount
}

func InitializeUser() *User {
        wire.Build(
                NewName,
                NewPublicAccount,
                wire.Struct(new(User), "MyName", "MyPublicAccount"),
        )
        return &amp;User{}
}
</code></pre>
<p>上述代码中,首先定义了自定义类型 <code>Name</code> 和 <code>PublicAccount</code> 以及结构体类型 <code>User</code>,并分别提供了 <code>Name</code> 和 <code>PublicAccount</code> 的初始化函数(<code>providers</code>)。然后定义一个注入器(<code>injectors</code>)<code>InitializeUser</code>,用于构造连接提供者并构造 <code>*User</code> 实例。</p>
<p>使用 <code>wire.Struct</code> 函数需要传递两个参数,第一个参数是结构体类型的指针值,另一个参数是一个可变参数,表示需要注入的结构体字段的名称集。</p>
<p>根据上述代码,使用 <code>Wire</code> 工具生成的代码如下所示:</p>
<pre><code class="language-go">func InitializeUser() *User {
    name := NewName()
    publicAccount := NewPublicAccount()
    user := &amp;User{
       MyName:          name,
       MyPublicAccount: publicAccount,
    }
    return user
}
</code></pre>
<p>如果我们不想返回指针类型,只需要修改 <code>InitializeUser</code> 函数的返回值为非指针即可。</p>
<h3 id="83-绑定值">8.3 绑定值</h3>
<p>有时,将基本值(通常为nil)绑定到类型是有用的。你可以向提供程序集添加一个<strong>值表达式</strong>,而不是让注入器依赖于一次性函数提供者(<code>providers</code>)。</p>
<pre><code class="language-go">func InjectUser() User {
    wire.Build(wire.Value(User{MyName: "小米SU7"}))
    return User{}
}
</code></pre>
<p>在上述代码中,使用 <code>wire.Value</code> 函数通过表达式直接指定 <code>MyName</code> 的值,生成的代码如下所示:</p>
<pre><code class="language-go">func InjectUser() User {
    user := _wireUserValue
    return user
}

var (
    _wireUserValue = User{MyName: "小米SU7"}
)
</code></pre>
<p>需要注意的是,值表达式将被复制到生成的代码文件中。</p>
<p>对于接口类型,可以使用 <code>InterfaceValue</code>:</p>
<pre><code class="language-go">func InjectPostService() service.IPostService {
    wire.Build(wire.InterfaceValue(new(service.IPostService), &amp;service.PostService{}))
    return nil
}
</code></pre>
<h3 id="84-使用结构体字段作为提供者providers">8.4 使用结构体字段作为提供者(providers)</h3>
<p>有些时候,你可以使用结构体的某个字段作为提供者,从而生成一个类似 <code>GetXXX</code> 的函数。</p>
<pre><code class="language-go">func GetUserName() Name {
    wire.Build(
       NewUser,
       wire.FieldsOf(new(User), "MyName"),
    )
    return ""
}
</code></pre>
<p>你可以使用 <code>wire.FieldsOf</code> 函数添加任意字段,生成的代码如下所示:</p>
<pre><code class="language-go">func GetUserName() Name {
    user := NewUser()
    name := user.MyName
    return name
}

func NewUser() User {
    return User{MyName: Name("小米SU7"), MyPublicAccount: PublicAccount("新一代车神!")}
}
</code></pre>
<h3 id="85-清理函数">8.5 清理函数</h3>
<p>如果一个提供者创建了一个需要清理的值(例如关闭一个文件),那么它可以返回一个闭包来清理资源。注入器会用它来给调用者返回一个聚合的清理函数,或者在注入器实现中稍后调用的提供商返回错误时清理资源。</p>
<pre><code class="language-go">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
}
</code></pre>
<h3 id="86-备用注入器语法">8.6 备用注入器语法</h3>
<p>如果你不喜欢在注入器函数声明的末尾编写类似<code>return Foo{}, nil</code>的语句,那么你可以简单粗暴地使用<code>panic</code>:</p>
<pre><code class="language-go">func InitializeGin() *gin.Engine {
    panic(wire.Build(/* ... */))
}
</code></pre>
<h2 id="九参考文档">九、参考文档</h2>
<ul>
<li>掘金依赖注入工具-wire</li>
<li>李文周的博客-依赖注入工具-wire</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    分享是一种快乐,开心是一种态度!<br><br>
来源:https://www.cnblogs.com/taoxiaoxin/p/18119294
頁: [1]
查看完整版本: Go 项目依赖注入wire工具最佳实践介绍与使用