当前位置: 首页 > 工具软件 > Wire > 使用案例 >

GO——wire(依赖注入)工具原理

徐弘图
2023-12-01

摘要

wire是由 google 开源的一个供 Go 语言使用的依赖注入代码生成工具。它能够根据你的代码,生成相应的依赖注入 go 代码。除了wire,Go的依赖注入框架还有Uber的Dig和Facebook的Inject,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection),而wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection)。使用反射带来的性能损失倒是其次,更重要的是反射使得代码难以追踪和调试(反射会令Ctrl+左键失效...)。而wire生成的代码是符合程序员常规使用习惯的代码,十分容易理解和调试。wire 能在编译期(准确地说是代码生成时)如果依赖注入有问题,在代码生成时即可报出来,不会拖到运行时才报,更便于 debug。

一、依赖注入的原理

依赖注入 ,英文全名是 dependency injection,简写为 DI。在用编程语言编写程序时,比如用 java 语言,会编写很多类,这些类之间相互调用,完成一个具体的功能。

如果mysql 获取数据,那么 mysql 数据库的用户名,密码,地址等等这些配置信息,也是需要的,我们构建一个 MySQL 类:

package com.wire.mysql

class MySQL {
    // mysql的配置
    getMySQLConfig() {
        port = 3306;
        user = "xxx";
        pass = "xxx";
    }
    // mysql的初始化
    initMySQL(){}
    // mysql的执行操作
    querySQL(){}
}

上面的 MySQL 操作类程序有什么不妥的地方?我们知道软件设计编程原则里有一个原则就是:单一职责。也就是说一个类最好只干一件事情。根据这个原则在看看 MySQL 类,里面有获取数据库配置数据,也有操作MySQL的方法,不是单一职责的。那里面获取数据库配置数据,可不可以单独拎出来用一个类表示? 当然可以。因为 MySQL 配置数据,多数是从文件里读取的,上面 MySQL 类是写死,这也是不合理的一个地方。而配置文件的来源,可以是 yml 格式文件,也可以是 xml 格式文件,还可以是远程文件。

修改上面的类,增加一个获取数据库配置的类:

package com.wire.mysql

class MySQLConfig {

    getMySQLConfig() {
        // 从配置文件获取 mysql 配置数据
    }
}
package com.wire.mysql

class MySQL {

    initMySQL(){
     // 获取数据库的配置信息
     mysqlconfig = new MySQLConfig();
    }
   
    querySQL(){}
}

对于上面改写后的类有什么不妥的地方?获取mysql的配置信息,是不是要在 MySQL 类里 new一下, 实例化一下,如果不在同一个包下,还要把配置类引入进来在才能实例化。或许可以进一步优化,直接把数据库配置类注入到 MySQL 操作类里。这就是依赖注入。

1.1 依赖是什么?注入又是什么?

  • mysql 操作类依赖谁?依赖数据库配置类。
  • 注入什么?把数据库配置类注入到 mysql 操作类里。
  • 注入是一个动作,把一个类注入到另外一个类。
  • 依赖是一种关系,类关系,一个类要完全发挥作用,需要依赖另外一个类。

要完成数据操作,mysql操作类是需要依赖数据库配置类的,把数据库配置类注入到mysql操作类里,就可以完成操作类功能。

package com.wire.mysql

class MySQL {

    private MySQLConfig config

    // 数据库配置类这里注入到mysql操作类里
    MySQL(MySQLConfig mysqlconfig) { 
        config = mysqlconfig
    }

    initMySQL(){
    
    }
   
    querySQL(){}
}

把数据库配置类注入到mysql操作类里。写 java 的人都知道 java 框架里有一个 spring 全家桶,spring 框架包核心有2个,其中有一个核心就是 IoC,另一个是 aop。IoC 的全称:Inversion of Control,控制反转。这个控制反转也是面向对象编程原则之一。但是这个控制反转比较难理解,如果结合上面的 DI 来理解,就比较容易理解点。可以把 DI 看作是 IoC 编程原则的一个具体实现。

依赖注入还可以从另外的软件设计思想来理解:1:分离关注点,2:高内聚,低耦合。对数据库 mysql 的操作和 mysql 的配置信息,这个 2 个是可以相互独立,相分离的。

1.2 何时使用依赖注入

当你的项目规模不大,文件不是很多,一个文件调用只需要传入少量依赖对象时,这时使用依赖注入就会使程序变得繁琐。

当规模变大,单个对象使用需要调用多个依赖对象时,而这些依赖又有自己依赖对象,这时对象创建变得繁琐,那么这时候依赖注入就可以出场了。

二、wire原理

wire 是编译代码时生成代码的依赖注入,是编译期间注入依赖代码(compile-time dependency injection)。而且代码生成期间,如果依赖注入有问题,生成依赖代码时就会出错,就可以报出问题来,而不必等到代码运行时才暴露出问题。

2.1 provider

首先,需要理解wire的 2 个核心概念:provider:提供一个对象。injector:负责根据对象依赖关系,生成新程序。从上面 java 模拟依赖注入的例子中,可以简化出依赖注入的步骤:

  • 第一:需要 New 出一个类实例
  • 第二:把这个 New 出来的类实例通过构造函数或者其他方式“注入”到需要使用它的类中
  • 第三:在类中使用这个 New 出来的实例

provider 是一个普通的 Go 函数 ,可以理解为是一个对象的构造函数。为下面生成 injector 函数提供”构件“。

// NewUserStore 是一个 provider for *UserStore,*UserStore 依赖 *Config,*mysql.DB

func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {... ...}

// NewDefaultConfig 是一个 provider for *Config,没有任何依赖

func NewDefaultConfig() *Config {...}

// NewDB 是 *mysql.DB 的一个 provider ,依赖于数据库连接信息 *ConnectionInfo

func NewDB(info *ConnectionInfo) (*mysql.DB, error){...}

provider 可以组合成一组 provider set。对于经常在一起使用的 providers 来说,这个非常有用。使用 wire.NewSet 方法可以把他们组合在一起,

wire.NewSet() 函数:这个函数可以把相关的 provider 组合在一起然后使用。当然也可以单独使用,如 var Provider = wire.NewSet(NewDB)。这个 NewSet 函数的返回值也可以作为其他 NewSet 函数的参数使用,比如上面的 SuperSet 作为参数使用。

var SuperSet = wire.NewSet(NewUserStore, NewDefaultConfig)

你也可以把其他的 provider sets 加入一个 provider set,

import (
    “example.com/some/other/pkg”
)
// ... ...
var MegaSet = wire.NewSet(SuperSet, pkg.OtherSet)

2.2 injector

我们编写程序把这些 providers 组合起来(比如下面例子 initUserStore() 函数),wire 里的 wire 命令会按照依赖顺序调用 providers 生成更加完整的函数,这个就是 injector。首先,编写生成 injector 的签名函数,然后用 wire 命令生成相应的函数。

  • wire 命令:直接在生成 injector 函数的包下,使用 wire 命令,就可以生成 injector 代码。
  • wire.Build() 函数:它的参数可以是 wire.NewSet() 组织的一个或多个 provider,也可以直接使用 provider。
// +build wireinject
func initUserStore(info *ConnectionInfo) (*UserStore, error) {
    // 声明获取 UserStore 需要调用哪些 provider 函数
    wire.Build(SuperSet, NewDB) 
    return nil, nil
}

然后用 wire 命令把上面的 initUserStore 函数生成 injector 函数,生成的函数对应文件名 wire_gen.go。

2.3 wire 使用

go get github.com/google/wire/cmd/wire

func Build(...interface{}) string

type Binding
type ProvidedValue
type ProviderSet
type StructFields
type StructProvider

func Bind(iface, to interface{}) Binding
func InterfaceValue(typ interface{}, x interface{}) ProvidedValue
func Value(interface{}) ProvidedValue
func NewSet(...interface{}) ProviderSet
func FieldsOf(structType interface{}, fieldNames ...string) StructFields
func Struct(structType interface{}, fieldNames ...string) StructProvider

三、wire的多种方式的依赖注册

3.1

3.2

3.3

博文参考

https://github.com/google/wire

 类似资料: