google 出品的依赖注入库 wire:https://github.com/google/wire

什么是依赖注入

依赖注入 ,英文全名是 dependency injection,简写为 DI。

百科解释:

依赖注入是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。

在用编程语言编写程序时,比如用 java 语言,会编写很多类,这些类之间相互调用,完成一个具体的功能。

例如,从 MySQL 获取数据,那么需要一个 MySQL 操作类 。

第一次编写mysql操作类:

class MySQL{
}

要从 mysql 获取数据,那么 mysql 数据库的用户名,密码,地址等等这些配置信息,也是需要的,继续编写 MySQL 类:

package com.demo.mysql

class MySQL {
getMySQLConfig() {
port = 3306;
username = "xxx";
password = "xxx";
} initMySQL(){} querySQL(){}
}

进一步思考,上面的 MySQL 操作类程序有什么不妥的地方?

编程原则里有一个原则就是:单一职责

也就是说一个类最好只干一件事情。

根据这个原则在看看 MySQL 类,里面有获取数据库配置数据,也有操作MySQL的方法,不是单一职责的。

那里面获取数据库配置数据,可不可以单独拎出来用一个类表示? 当然可以。

因为 MySQL 配置数据,多数是从文件里读取的,上面 MySQL 类是写死,这也是不合理的一个地方。

而配置文件的来源,可以是 yml 格式文件,也可以是 toml 格式文件,还可以是远程文件。

第二次编写mysql操作类:

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

package com.demo.mysql

class MySQLConfig {
getMySQLConfig() {
// 从配置文件获取 mysql 配置数据
}
}

获取数据的类变成:

package com.demo.mysql

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

思考一下,上面改写后的类有什么不妥的地方?

获取mysql的配置信息,是不是要在 MySQL 类里 new一下, 实例化一下,如果不在同一个包下,还要把配置类引入进来在才能实例化。这里能不能优化下,当然可以。

直接把数据库配置类注入到 MySQL 操作类里。这就是依赖注入

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

    mysql 操作类依赖谁?依赖数据库配置类。

    注入什么?把数据库配置类注入到 mysql 操作类里。

    注入是一个动作,把一个类注入到另外一个类。

    依赖是一种关系,类关系,一个类要完全发挥作用,需要依赖另外一个类。

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

第三次编写mysql操作类:

伪代码示例:

package com.demo.mysql

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

把数据库配置类注入到mysql操作类里。

写 java 的人都知道 java 框架里有一个 spring 全家桶,spring 框架包核心有2个,其中有一个核心就是 IoC,另一个是 aop。

IoC 的全称:Inversion of Control,控制反转。

这个控制反转也是面向对象编程原则之一。

但是这个控制反转比较难理解,如果结合上面的 DI 来理解,就比较容易理解点。

可以把 DI 看作是 IoC 编程原则的一个具体实现。

依赖注入还可以从另外的软件设计思想来理解:

  1. 分离关注点
  2. 高内聚,低耦合

对数据库 mysql 的操作和 mysql 的配置信息,这个 2 个是可以相互独立,相分离的。

何时使用依赖注入

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

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

wire 概念说明

wire 简介

wire 是由 google 开源的一个用 Go 语言实现的依赖注入代码生成工具。它能够根据你写的代码生成相应的依赖注入 Go 代码。

与其他依赖注入工具不同,比如 uber 的 dig 和 facebook 的 inject,这 2 个工具都是使用反射实现的依赖注入,而且是运行时注入(runtime dependency injection)。

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

provider 和 injector

首先,需要理解 wire 的 2 个核心概念:provider 和 injector。

从上面 java 模拟依赖注入的例子中,可以简化出依赖注入的步骤:

第一:需要 New 出一个类实例

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

第三:在类中使用这个 New 出来的实例

从上面步骤来理解 wire 的 2 个核心概念 provider 和 injector。

provider 就相当于上面 New 出来的类实例。

injector 就相当于“注入”动作前,把所需依赖函数进行聚合,根据这个聚合的函数生成依赖关系。

provider:提供一个对象。

injector:负责根据对象依赖关系,生成新程序。

provider

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

看下面例子,来自 go blog

这篇 blog 是 2018.10.9 发表,可能一些信息有点老,再参考 github guide ,这篇 guide 最后更新于 2021.1.26。

下面的 NewUserStore() 函数可以看作是一个 provider。这个函数需要传入 *Config 和 *mysql.DB 2 个参数。

// 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 方法可以把他们组合在一起,

var SuperSet = wire.NewSet(NewUserStore, NewDefaultConfig)

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

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

wire.NewSet() 函数:

这个函数可以把相关的 provider 组合在一起然后使用。当然也可以单独使用,如 var Provider = wire.NewSet(NewDB)。

这个 NewSet 函数的返回值也可以作为其他 NewSet 函数的参数使用,比如上面的 SuperSet 作为参数使用。

injector

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

首先,编写生成 injector 的签名函数,然后用 wire 命令生成相应的函数。

例子如下:

// +build wireinject

func initUserStore(info *ConnectionInfo) (*UserStore, error) {
wire.Build(SuperSet, NewDB) // 声明获取 UserStore 需要调用哪些 provider 函数
return nil, nil
}

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

wire 命令:

You can generate the injector by invoking Wire in the package directory。

直接在生成 injector 函数的包下,使用 wire 命令,就可以生成 injector 代码。

wire.Build() 函数:

它的参数可以是 wire.NewSet() 组织的一个或多个 provider,也可以直接使用 provider。

wire 使用

wire 结构体和方法列表

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

更详细说明可以看这里 func index - pkg.go.dev

wire 安装

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

快速开始

例子1

先新建一个 basics 的文件夹,然后在 basics 里使用 go mod init basics,新建一个 go.mod,在 go.mod 里引入 wire:require github.com/google/wire v0.5.0

整个文件夹目录结构:

  1. 定义 providers

在 basics 文件夹下新建 basics.go 文件,写入如下代码:

package main

import (
"context"
"errors"
) type Student struct {
ClassNo int
} // NewStudent 就是一个 provider,返回一个 Student
func NewStudent() Student {
return Student{ClassNo: 10}
} type Class struct {
ClassNo int
} // NewClass 就是一个 provider,返回一个 Class
func NewClass(stu Student) Class {
return Class{ClassNo: stu.ClassNo}
} type School struct {
ClassNo int
} // NewSchool 是一个 provider,返回一个 School
// 与上面 provider 不同的是,它还返回了一个错误信息
func NewSchool(ctx context.Context, class Class) (School, error) {
if class.ClassNo == 0 {
return School{}, errors.New("cannot provider school when class is 0")
}
return School{ClassNo: class.ClassNo}, nil
}
  1. 定义 injector

新建文件 wire.go,代码如下:

// +build wireinject

package main

import (
"github.com/google/wire"
) var SuperSet = wire.NewSet(NewStudent, NewClass, NewSchool) func initSchool() (School, error) {
wire.Build(SuperSet)
return School{}, nil
}

// +build wireinject

这一行代码一定要在包最上面声明,表明这是一个准备被编译的 injector

  1. 用 wire 命令生成 injector 函数代码

wire 命令生成 injector 代码,在 basics 目录下执行 wire 命令:

$ wire
wire: D:\work\mygo\go-practice2\di\wire\basics\wire.go:9:1: inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet" (D:\work\mygo\go-practice2\di\wire\basics\wire.go:7:16) wire: basics: generate failed
wire: at least one generate failure

报错了,看看显示出的错误信息,最主要是这一行信息:

inject initSchool: no provider found for context.Context needed by basics.School in provider set "SuperSet"

来看一看 initSchool 函数,果然没有给它提供 context.Context 。我们来修改函数,引入 context 包,然后给 initSchool 函数增加参数 context.Context

func initSchool(ctx context.Context) (School, error) {
wire.Build(SuperSet)
return School{}, nil
}

再来用命令 wire 编译:

$ wire
wire: basics: wrote D:\work\mygo\go-practice2\di\wire\basics\wire_gen.go

生成的 injector 代码,wire_gen.go 文件,

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject package main import (
"context"
"github.com/google/wire"
) // Injectors from wire.go: func initSchool(ctx context.Context) (School, error) {
student := NewStudent()
class := NewClass(student)
school, err := NewSchool(ctx, class)
if err != nil {
return School{}, err
}
return school, nil
} // wire.go: var SuperSet = wire.NewSet(NewStudent, NewClass, NewSchool)

小结

wire 使用的步骤:

  1. 先编写 provider。
  2. 再编写 injector:把相关 provider 组织在一起,成为一个 ProviderSet。
  3. 最后用 wire 命令编译:wire 会根据 provider 之间相关依赖生成代码。

wire.NewSet 函数:

它可以把 provider 集合起来。作用1分类:可以把一组相关的 provider 写在一起组成 ProviderSet。作用1延伸第2个作用,避免 provider 过多难于管理。

wite.Build 函数:

func Build(...interface{}) string

它的参数是 provider 不定长列表。 把所有相关的 provider 组织在一起然后生成 injector 函数代码。它是生成 injector 函数的模板函数。

绑定接口

上面例子1绑定的是结构体和构造函数。如果有接口 interface 参与呢,那怎么办?比如下面的代码,

type Fooer interface {
Hello()
} type Foo struct{} func (f Foo)Hello() {
fmt.Println("hello")
} func Bar struct{} func NewBar() Bar {
return Bar{}
}

有接口 Fooer,这个怎么绑定呢?这时候就可以用 [wire.Bind](wire/wire.go at v0.5.0 · google/wire · GitHub) 函数,

var bind = wire.Bind(new(Fooer), new(Foo))
var set = wire.NewSet(bind, NewBar) // or
var set = wire.NewSet(wire.Bind(new(Fooer), new(Foo)), NewBar)

struct prividers

struct 也可以直接当作一个 provider 使用。如果结构体的 provider 仅仅是用作字段赋值,那么可以使用函数 wire.Struct 来赋值。

type Foo int
type Bar int func NewFoo() Foo {/* ... */}
func NewBar() Bar {/* ... */} type FooBar struct {
MyFoo Foo
MyBar Bar
} var set = wire.NewSet(
NewFoo,
NewBar,
wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)

更多信息请参考struct providers guide

Provider Set

上面例子1中就用到 provider set,把

相关的 provider 组织在一起。使用函数 wire.NewSet 就可以做到。

更多例子请查看官方文档:

https://github.com/google/wire

参考

golang常用库包:Go依赖注入(DI)工具-wire使用的更多相关文章

  1. 依赖注入(DI)和Ninject

    [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject 本文目录: 1.为什么需要依赖注入 2.什么是依赖注入 3.使用NuGet安装库 4.使用Ninject的一般步骤 5. ...

  2. Spring框架学习笔记(1)——控制反转IOC与依赖注入DI

    Spring框架的主要作用,就是提供了一个容器,使用该容器就可以创建并管理对象.比如说Dao类等,又或者是具有多依赖关系的类(Student类中包含有Teacher类的成员变量) Spring有两个核 ...

  3. 依赖注入 DI 控制反转 IOC MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. 依赖注入 DI 控制反转 IOC 概念 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  5. 轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)

    前言 关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享. 依赖注入和控制反转两个概念让很多初学这迷惑, 觉得玄之又玄,高深莫测. 这里想先说明两点: 依赖注入和控制反转不是高级 ...

  6. 控制反转IOC与依赖注入DI【转】

    转自:http://my.oschina.net/1pei/blog/492601 一直对控制反转.依赖注入不太明白,看到这篇文章感觉有点懂了,介绍的很详细. 1. IoC理论的背景我们都知道,在采用 ...

  7. MVC进阶之路:依赖注入(Di)和Ninject

    MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...

  8. 控制反转IOC与依赖注入DI - 理论篇

    学无止境,精益求精 十年河东十年河西,莫欺少年穷 昨天是五一小长假归来上班的第一天,身体疲劳,毫无工作热情.于是就看看新闻,喝喝茶,荒废了一天 也就在昨天,康美同事张晶童鞋让我学习下IOC的理论及实现 ...

  9. 20181123_控制反转(IOC)和依赖注入(DI)

    一.   控制反转和依赖注入: 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节) 如果要想做到控制反转(IOC), 就必须要使 ...

随机推荐

  1. 详解数据库三种删除方法: delete drop truncate

    Delete Delete :删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行). 删除某一行:Delete from 数据表名称 where 列名称=值: 删除所有行:Del ...

  2. [SPDK/NVMe存储技术分析]002 - SPDK官方介绍

    Introduction to the Storage Performance Development Kit (SPDK) | SPDK概述 By Jonathan S. (Intel), Upda ...

  3. 【VNCTF2022】Reverse wp

    babymaze 反编译源码 pyc文件,uncompy6撸不出来,看字节码 import marshal, dis fp = open(r"BabyMaze.pyc", 'rb' ...

  4. 前端工程化 Webpack基础

    前端工程化 模块化 (js模块化,css模块化,其他资源模块化) 组件化 (复用现有的UI结构.样式.行为) 规范化 (目录结构的划分.编码规范化.接口规范化.文档规范化.Git分支管理) 自动化 ( ...

  5. leetcode-3无重复字符的最长子串

    题目原题: 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 ...

  6. Mac安装和配置Maven 及其第二次启动报错问题解决

      1.下载安装 下载地址: https://maven.apache.org/download.cgi 下载后解压下来重名名为ApacheMaven,并放入到/usr/local/下 2.配置环境变 ...

  7. 如何将Matlab中“模糊控制设计器”的隶属度函数导出图片(figure)

    如何将Matlab中"模糊控制设计器"的隶属度函数导出图片(figure)详情参考matlab官方帮助手册:plotmf()函数https://www.mathworks.com/ ...

  8. 顺利通过EMC实验(1)

  9. Web最佳实践阅读总结(1)

    介绍 最近开始刷一些书和题,此系列是介绍在读Web最佳实践的一些收获和体会. web前端发展现状 存在问题: 代码组织混乱 代码格式的问题突出 页面布局随意 网站整体性能差,没有意识到应用诸如缓存,动 ...

  10. HTML入门学习笔记(二)

    第三章 文本 段落 p <p>毫不奇怪,p是最常用到的HTML元素之一</p> 作者联系信息 address address并不是用于标记邮政地址,而是定义与HTML页面或页面 ...