前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分解uber依赖注入库dig-源码分析

分解uber依赖注入库dig-源码分析

原创
作者头像
lpxxn
修改2021-05-10 14:25:16
8040
修改2021-05-10 14:25:16
举报
文章被收录于专栏:技术之路技术之路

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

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

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

整体调用流程

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

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

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

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

代码语言:txt
复制
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 []*Studentgroup:"stu,flatten",在Invoke时怎么处理?

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

分析传入的函数

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

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

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

paramList

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

代码语言:txt
复制
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组类型,比如上一篇帖子中的例子

代码语言:txt
复制
container.Provide(NewUser("tom", 3), dig.Group("stu"))
和
		StudentList []*Student `group:"stu"`

都是组类型。

paramObject

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

代码语言:txt
复制
	type DBInfo struct {
		dig.In
		PrimaryDSN   *DSN `name:"primary"`
		SecondaryDSN *DSN `name:"secondary"`
	}

paramObject可以包含 paramSingleparamGroupedSlice类型。

resultList

代码语言:txt
复制
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组类型

比如上一篇帖子中的

代码语言:txt
复制
container.Provide(NewUser("tom", 3), dig.Group("stu"))
和
StudentList []*Student `group:"stu"`
resultObject

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

代码语言:txt
复制
	type DSNRev struct {
		dig.Out
		PrimaryDSN   *DSN `name:"primary"`
		SecondaryDSN *DSN `name:"secondary"`
	}

resultObject可以包含resultSingleresultGrouped

容器

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

代码语言:txt
复制
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对应的函数的返回值

代码语言:txt
复制
type key struct {
	t reflect.Type
	// Only one of name or group will be set.
	name  string
	group string
}

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

看这一段代码

代码语言:txt
复制
	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,因为像组那样的返回值是有多个函数提供的。

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

代码语言:txt
复制
	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赋值

代码语言:txt
复制
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)
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 整体调用流程
  • 分析传入的函数
    • paramList
      • paramSingle
      • paramGroupedSlice
      • paramObject
    • resultList
      • resultList
      • resultSingle
      • resultGrouped
      • resultObject
  • 容器
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档