分解uber依赖注入库dig-源码分析
上一篇帖子 分解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
信息,再调用node
的Call
方法进行递规的调用。
形参有下面几种类型
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
可以包含 paramSingle
和paramGroupedSlice
类型。
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
可以包含resultSingle
和resultGrouped
容器
在调用container := dig.New()
的时候就会创建一个容器,所有Provide
进行注册的函数都会组成容器的节点node
,node
组成了`容器的核心
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-源码分析的更多相关文章
- 分解uber依赖注入库dig-使用篇
golang的依赖注入库非常的少,好用的更是少之又少,比较好用的目前有两个 谷歌出的wire,这个是用抽象语法树在编译时实现的. uber出的dig,在运行时,用返射实现的,并基于dig库,写了一个依 ...
- Spring源码分析之循环依赖及解决方案
Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...
- Spark源码分析 -- TaskScheduler
Spark在设计上将DAGScheduler和TaskScheduler完全解耦合, 所以在资源管理和task调度上可以有更多的方案 现在支持, LocalSheduler, ClusterSched ...
- Spring源码分析专题——目录
Spring源码分析专题 -- 阅读指引 IOC容器 Spring源码分析专题 -- IOC容器启动过程(上篇) Spring源码分析专题 -- IOC容器启动过程(中篇) Spring源码分析专题 ...
- Spring源码分析之`BeanFactoryPostProcessor`调用过程
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- ABP源码分析六:依赖注入的实现
ABP的依赖注入的实现有一个本质两个途径:1.本质上是依赖于Castle这个老牌依赖注入的框架.2.一种实现途径是通过实现IConventionalDependencyRegistrar的实例定义注入 ...
- [Abp 源码分析]三、依赖注入
0.简要介绍 在 Abp 框架里面,无时无刻不存在依赖注入,关于依赖注入的作用与好处我就不在这里多加赘述了,网上有很多解释的教程.在 [Abp 源码分析]一.Abp 框架启动流程分析 里面已经说过,A ...
- Spring 源码分析之 bean 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
随机推荐
- slickgrid ( nsunleo-slickgrid ) 4 解决区域选择和列选择冲突
slickgrid ( nsunleo-slickgrid ) 3 解决区域选择和列选择冲突 之前启用区域选择的时候,又启用了列选择(CheckboxSelectColumn),此时发现选择状态与区域 ...
- Windows搭建flutter开发环境以及android&idea配置
Flutter:是谷歌新推出的一款能够支持Android和IOS跨平台开发的全新的UI框架. 拥有自己的一套UI渲染引擎,所以目前的测试数据来看,在性能上面,并没有比原生App性能低多少,所以目前来看 ...
- visual studio 2019 + cmake 实现windows linux跨平台开发环境搭建
visual studio 2019开始支持cmake跨平台开发. 以前cmake项目需要先生成vs项目,然后vs直接使用vs项目开发.现在可以直接用vs2019创建或打开cmake项目. 使用vis ...
- Quartz基础使用
Quartz基本组成部分: 调度器:Scheduler 任务:JobDetail 触发器:Trigger,包括SimpleTrigger和CronTrigger . using Quartz; usi ...
- Linux wget 使用笔记
wget 是 Linux 上最常用的文件下载工具,简单实用.记录一下一些常用的操作备查. 最常用最简单的操作就是直接使用一个 URL 下载 下载互联网上指定的文件 wget https://gemme ...
- 后台开发-核心技术与应用实践--TCP协议
网络模型 为使不同计算机厂家的计算机能够互相通信,国际标准化组织 ISO 1981 年正式推荐了一个网络系统结构一一七层参考模型,也叫作开放系统互连模型. ISO 七层网络模型及其功能展示: 这个七层 ...
- Android 在活动中使用 Menu
•前行必备--创建 menu 首先,新建一个项目,选择 Empty Activity 选项,并命名为 Test Menu: 这样 Android Studio 自动为我们新建 MainActivity ...
- [贪心]D. 【例题4】国王游戏
D . [ 例 题 4 ] 国 王 游 戏 D. [例题4]国王游戏 D.[例题4]国王游戏 解析 贪心思想,考虑交换后的值比交换前的小. 然后数据规模用高精度 Code #include <b ...
- [矩阵乘法]裴波拉契数列II
[ 矩 阵 乘 法 ] 裴 波 拉 契 数 列 I I [矩阵乘法]裴波拉契数列II [矩阵乘法]裴波拉契数列II Description 形如 1 1 2 3 5 8 13 21 34 55 89 ...
- Leedcode算法专题训练(数组与矩阵)
1. 把数组中的 0 移到末尾 283. Move Zeroes (Easy) Leetcode / 力扣 class Solution { public void moveZeroes(int[] ...