上一篇帖子 分解uber依赖注入库dig-使用篇 把如何使用dig进行代码示例说明,这篇帖子分析dig的源码,看他是如何实现依赖注入的。

dig实现的中心思想:所有传入Provide的函数必须要有除error外的返回参数,返回参数供其他函数的形参使用。

比如上一篇的第一个例子里,一个函数func() (*Config, error)返回Config 另一个函数func(cfg *Config) *log.Logger的形参使用了Config

整体调用流程

简单说一下整体的调用流程,具体的细节再一点点展开说明。

传入给Provide里的函数并不会直接被调用,dig只会对这些函数进行分析,提取函数的形参和返回参数,根据返回参数来组织容器结构(这个后面会详细说)。只有在调用Invoke的时候才

会根据传入的函数的形参进行查询和调用返回这些形参的函数。还以上一篇的第一个例子进行说明

一共有两个Provide方法进行了函数注册

c.Provide(func() (*Config, error))
c.Provide(func(cfg *Config) *log.Logger)

调用Invoke方法c.Invoke(func(l *log.Logger))Invoke方法,通过对传入函数形参的分析,形参里有*log.Logger去容器里找哪个函数的返回类型有*log.Logger,找到方法func(cfg *Config) *log.Logger

发现这个函数有形参cfg *Config再去找返回参数有*Config的函数,找到了func() (*Config, error)形参为空,停止查询,进行函数的调用,把返回的*Config传递给func(cfg *Config) *log.Logger,进行

方法调用再把返回的*log.Logger传给c.Invoke(func(l *log.Logger))进行函数的调用执行



所以在写Prvoide注册函数的时候,顺序随便写也不会问题,只要Invoke时能查找到相应的函数就可以。

上面简单说了一下流程,提一个问题:如果是组参数,比如上一篇-组的例子只有多个函数返回了StudentList []*Student group:"stu,flatten"``,在Invoke时怎么处理?

先留一个扣子,下面的内容会进行详细说明。

分析传入的函数

Provide把函数添加到容器内,dig会把传入的函数进行分析,



利用go的反射机制,提取函数的形参和返回参数组成一个node,下图是node所有字段的详细说明



主要看一下形参paramList和返回参数resultList两个字段

paramList

一个函数所有的形参信息都会放入到paramList

type param interface {
fmt.Stringer
// 构建所有依赖的函数,调用返回函数的值
Build(containerStore) (reflect.Value, error)
// 在生成dot文件时使用
DotParam() []*dot.Param
}

Build方法是很重要的一个方法,他会构建所有依赖的函数,调用返回函数的值,比如注入函数c.Provide(func(cfg *Config) *log.Logger) 的形参cfg *Config会被解析为paramList的一个元素,在调用Build方法时,

会去容器里查找有返回*log.Logger的注入函数的node信息,再调用nodeCall方法进行递规的调用。

形参有下面几种类型

paramSingle

paramSingle好理解,注入函数的一般形参比如int、string、struct、slice都属于paramSingle

paramGroupedSlice

paramGroupedSlice组类型,比如上一篇帖子中的例子

container.Provide(NewUser("tom", 3), dig.Group("stu"))

StudentList []*Student `group:"stu"`

都是组类型。

paramObject

paramObject 嵌入dig.In的结构体类型,比如上一篇帖子中的例子

	type DBInfo struct {
dig.In
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}

paramObject可以包含 paramSingleparamGroupedSlice类型。

resultList

type result interface {
// Extracts the values for this result from the provided value and
// stores them into the provided containerWriter.
Extract(containerWriter, reflect.Value) // 生成dot文件时调用
DotResult() []*dot.Result
}

Extract(containerWriter, reflect.Value)从容器里提取到相应类型并给他赋值,比如注入函数c.Provide(func(cfg *Config) *log.Logger)*log.Logger是一个resultSingle,在调用Extract时就是把reflect.Value的值赋给他。

返回参数有下面几种类型

resultList

node的所有返回参数都保存在resultList

resultSingle

resultSingle 单独的一个返回参数,注入函数的一般返回参数比如int、string、struct、slice都属于他

resultGrouped

resultGrouped组类型

比如上一篇帖子中的

container.Provide(NewUser("tom", 3), dig.Group("stu"))

StudentList []*Student `group:"stu"`

resultObject

resultObject 嵌入dig.Out的结构体类型,上一篇的例子中

	type DSNRev struct {
dig.Out
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}

resultObject可以包含resultSingleresultGrouped

容器

在调用container := dig.New()的时候就会创建一个容器,所有Provide进行注册的函数都会组成容器的节点nodenode组成了`容器的核心

type Container struct {
providers map[key][]*node
nodes []*node
values map[key]reflect.Value
groups map[key][]reflect.Value
rand *rand.Rand
isVerifiedAcyclic bool
deferAcyclicVerification bool
invokerFn invokerFn
}



providers map[key][]*node这个key是非常重要的一个参数,他是node对应的函数的返回值

type key struct {
t reflect.Type
// Only one of name or group will be set.
name string
group string
}

name命名参数和group组不能同时存在,上一篇代码示例的时候就有说过。

看这一段代码

	case resultSingle:
k := key{name: r.Name, t: r.Type}
cv.keyPaths[k] = path
// .......
case resultGrouped:
k := key{group: r.Group, t: r.Type}
cv.keyPaths[k] = path
}

其中的t: r.Type就是返回值参数的类型,也就是说是providers map[key][]*node这个字典,key是返回值信息[]*node是提供这个返回值的函数,为什么是个slice,因为像组那样的返回值是有多个函数提供的。

这里要说一下组是如何做的,也回答上面留的问题,我们的示例代码

	type Rep struct {
dig.Out
StudentList []*Student `group:"stu,flatten"`
}
if err := container.Provide(NewUser("tom", 3)); err != nil {
t.Fatal(err)
}
if err := container.Provide(NewUser("jerry", 1)); err != nil {
t.Fatal(err)

有多个函数返回了[]*Student,dig会解析成

key{name: "stu", t: 类型的Type},做为字典的key,有两个Provide里的注入函数,

在调用Extract方法时,给groups map[key][]reflect.Value赋值

func (rt resultGrouped) Extract(cw containerWriter, v reflect.Value) {
if !rt.Flatten {
cw.submitGroupedValue(rt.Group, rt.Type, v)
return
}
for i := 0; i < v.Len(); i++ {
cw.submitGroupedValue(rt.Group, rt.Type, v.Index(i))
}
} func (c *Container) submitGroupedValue(name string, t reflect.Type, v reflect.Value) {
k := key{group: name, t: t}
c.groups[k] = append(c.groups[k], v)
}

分解uber依赖注入库dig-源码分析的更多相关文章

  1. 分解uber依赖注入库dig-使用篇

    golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个 谷歌出的wire,这个是用抽象语法树在编译时实现的. uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依 ...

  2. Spring源码分析之循环依赖及解决方案

    Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...

  3. Spark源码分析 -- TaskScheduler

    Spark在设计上将DAGScheduler和TaskScheduler完全解耦合, 所以在资源管理和task调度上可以有更多的方案 现在支持, LocalSheduler, ClusterSched ...

  4. Spring源码分析专题——目录

    Spring源码分析专题 -- 阅读指引 IOC容器 Spring源码分析专题 -- IOC容器启动过程(上篇) Spring源码分析专题 -- IOC容器启动过程(中篇) Spring源码分析专题 ...

  5. Spring源码分析之`BeanFactoryPostProcessor`调用过程

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...

  6. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  7. ABP源码分析六:依赖注入的实现

    ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架.2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入 ...

  8. [Abp 源码分析]三、依赖注入

    0.简要介绍 在 Abp 框架里面,无时无刻不存在依赖注入,关于依赖注入的作用与好处我就不在这里多加赘述了,网上有很多解释的教程.在 [Abp 源码分析]一.Abp 框架启动流程分析 里面已经说过,A ...

  9. Spring 源码分析之 bean 依赖注入原理(注入属性)

         最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...

随机推荐

  1. cve-2019-2725 反序列化远程代码执行

    描述:部分版本WebLogic中默认包含的wls9_async_response包,为WebLogic Server提供异步通讯服务.由于该WAR包在反序列化处理输入信息时存在缺陷,攻击者可以发送精心 ...

  2. 自定义校验注解ConstraintValidator

    一 前言 系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的.所以我们可能会写大量的if else等判断逻辑,特别是在不同方法出现相同的数据时,校验的逻辑代码会反复出现,导致代码冗余, ...

  3. Cloudam云端,探索高性能计算在药物研究领域的解决方案

    近日,Cloudam云端与国内某知名药企与合作,通过接入Cloudam云端自主研发的云E云超算服务,计算效率提高的数百倍.这也是云算力在生命科学领域的又一次成功应用.Cloudam云端云E云超算服务是 ...

  4. java IO NIO BIO 最权威的总结

    1. BIO (Blocking I/O) 1.1 传统 BIO 1.2 伪异步 IO 1.3 代码示例 1.4 总结 2. NIO (New I/O) 2.1 NIO 简介 2.2 NIO的特性/N ...

  5. 保姆级教程!使用k3d实现K3s高可用!

    你是否曾经想尝试使用K3s的高可用模式?但是苦于没有3个"备用节点",或者没有设置相同数量的虚拟机所需的时间?那么k3d这个方案也许你十分需要噢! 如果你对k3d尚不了解,它的名字 ...

  6. java例题_28 冒泡排序

    1 /*28 [程序 28 排序算法] 2 题目:对 10 个数进行排序 3 程序分析:可以利用选择法,即从后 9 个比较过程中,选择一个最小的与第一个元素交换, 下次类推, 4 即用第二个元素与后 ...

  7. C# net Emgu.CV.World 人脸识别 根据照片将人脸抠图出来。

    Emgu.CV.World 人脸识别 根据照片将人脸抠图出来.效果如下: 应用范围:配合摄像头,抓取的图像,抠出人脸照片,这样人脸照片的大小会很小,传输速度快.这样识别速度也就快. 目前我正在做百度人 ...

  8. kubernetes中有状态应用的优雅缩容

    将有状态的应用程序部署到Kubernetes是棘手的. StatefulSet使它变得容易得多,但是它们仍然不能解决所有问题.最大的挑战之一是如何缩小StatefulSet而不将数据留在断开连接的Pe ...

  9. 痞子衡嵌入式:在i.MXRT启动头FDCB里调整Flash工作频率也需同步设Dummy Cycle

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Flash工作频率与Dummy Cycle的联系. 上一篇文章 <从头开始认识i.MXRT启动头FDCB里的lookupTable ...

  10. SpringBoot+Gradle构建多模块项目

    1 概述 Gradle由于构建速度比Maven快,且比Maven灵活,因此很多后端的应用都使用了Gradle进行构建,但一个问题是,Gradle的多模块项目比较难构建,再加上Gradle的更新非常快, ...