依赖注入的本质是通过分析依赖对象的构造函数的输入输出参数,解析出之间的联系,简化顶层对象的构造过程。
如何实现依赖注入业界有两种知名的方案,一种是google的wire(参考:wire 源码分析),通过分析被依赖的底层对象的构造函数的输入输出,解析出抽象语法树,然后通过代码生成的方式生成顶层对象的构造函数,开发者只需编写wire.go文件,然后用工具生成wire_gen.go文件,简化我们开发过程中对象之间依赖关系的处理。另外一种方案就是通过反射的方式首先注入依赖的对象的构造函数,然后在运行时invoke的时候,查找依赖属性,通过反射的方式来实现运行时的依赖注入,本文介绍的https://github.com/uber-go/dig 库就是其中比较知名的一种实现。并且在此基础上实现了依赖注入框架https://github.com/uber-go/fx,我下一次分析。
使用dig来实现依赖注入非常简单,分为三步:
// 创建 dig 对象
digObj := dig.New()
// 利用 Provide 注入依赖
digObj.Provide(NewA)
// 根据提前注入的依赖来生成对象
err := digObj.Invoke(assignD)
1,通过dig.New()来生成一个容器,这个容器是由一个个scope组成的有向无环图。
2,通过 Provide方法注入被依赖对象的构造函数,被依赖对象的构造函数的返回值的类型和类型名字被作为key,构造函数和一些相关上下文信息被作为value存在scope的熟悉providers这个map里面。
3,通过Invoke的输入函数的参数,到providers里面去找对应的类型的构造函数,然后通过反射的方式调用构造函数,完成依赖属性的初始化构造,这个过程是一个递归的流程。
当然,回过头来分析,我们发现,整个依赖注入的过程本质上是一个构造函数的寻找过程,和wire的原理有异曲同工之妙。不进令人反思,我们是不是在被依赖方标记下我可以提供底层被依赖对象的实例,在需要被构造的对象上标记出,我的属性需要去底层查找。同样也能完成依赖的注入。这就是dig的第二种注入方式:通过在依赖提供方嵌入dig.Out的匿名属性,在依赖方嵌入dig.In的匿名属性。
type DSNRev struct {
dig.Out
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
type DBInfo struct {
dig.In
PrimaryDSN *DSN `name:"primary"`
SecondaryDSN *DSN `name:"secondary"`
}
c := dig.New()
p1 := func() (DSNRev, error) {
return DSNRev{PrimaryDSN: &DSN{Addr: "Primary DSN"},
SecondaryDSN: &DSN{Addr: "Secondary DSN"}}, nil
}
if err := c.Provide(p1); err != nil {
panic(err)
}
了解完使用方法后,我们来开始分析源码:
1,创建容器的过程
New函数位置在go.uber.org/[email protected]/container.go中,它返回了一个容器类型。非常重要的属性就是scope
func New(opts ...Option) *Container {
s := newScope()
c := &Container{scope: s}
for _, opt := range opts {
opt.applyOption(c)
}
return c
}
容器就是依赖有向无环图的根
type Container struct {
// this is the "root" Scope that represents the
// root of the scope tree.
scope *Scope
}
其中scope属性的构造函数位于go.uber.org/[email protected]/scope.go
func newScope() *Scope {
s := &Scope{}
s.gh = newGraphHolder(s)
我们看下Scope这个结构体
type Scope struct {
// This implements containerStore interface.
// Name of the Scope
name string
// Mapping from key to all the constructor node that can provide a value for that
// key.
providers map[key][]*constructorNode
// Mapping from key to the decorator that decorates a value for that key.
decorators map[key]*decoratorNode
// constructorNodes provided directly to this Scope. i.e. it does not include
// any nodes that were provided to the parent Scope this inherited from.
nodes []*constructorNode
// Values that generated via decorators in the Scope.
decoratedValues map[key]reflect.Value
// Values that generated directly in the Scope.
values map[key]reflect.Value
// Values groups that generated directly in the Scope.
groups map[key][]reflect.Value
// Values groups that generated via decoraters in the Scope.
decoratedGroups map[key]reflect.Value
// Source of randomness.
rand *rand.Rand
// Flag indicating whether the graph has been checked for cycles.
isVerifiedAcyclic bool
// Defer acyclic check on provide until Invoke.
deferAcyclicVerification bool
// invokerFn calls a function with arguments provided to Provide or Invoke.
invokerFn invokerFn
// graph of this Scope. Note that this holds the dependency graph of all the
// nodes that affect this Scope, not just the ones provided directly to this Scope.
gh *graphHolder
// Parent of this Scope.
parentScope *Scope
// All the child scopes of this Scope.
childScopes []*Scope
}
它是一个多叉树结构,childScopes属性就是存孩子scope的指针数组。providers属性存我们前文提到的注入的依赖,decorators允许我们对一个对象进行装饰,这里就是存装饰方法的。invokerFn属性存我们进行Invoke的时候调用的方法。它的类型定义如下:
// invokerFn specifies how the container calls user-supplied functions.
type invokerFn func(fn reflect.Value, args []reflect.Value) (results []reflect.Value)
它输入函数是函数和函数对应的参数列表,返回的是函数的返回值列表。可以看下它的一个默认实现。
func defaultInvoker(fn reflect.Value, args []reflect.Value) []reflect.Value {
return fn.Call(args)
}
直接调用了reflect包的Call方法。源码位置位于go/src/reflect/value.go
func (v Value) Call(in []Value) []Value {
v.mustBe(Func)
v.mustBeExported()
return v.call("Call", in)
}
创建一个空白的scope后,初始化了它的gh属性
go.uber.org/[email protected]/graph.go
func newGraphHolder(s *Scope) *graphHolder {
return &graphHolder{s: s, snap: -1}
}
type graphHolder struct {
// all the nodes defined in the graph.
nodes []*graphNode
// Scope whose graph this holder contains.
s *Scope
// Number of nodes in the graph at last snapshot.
// -1 if no snapshot has been taken.
snap int
}
2,被依赖项注入的过程
func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error {
return c.scope.Provide(constructor, opts...)
}
容器直接调用了scope的Provide方法:go.uber.org/[email protected]/provide.go
func (s *Scope) Provide(constructor interface{}, opts ...ProvideOption) error {
ctype := reflect.TypeOf(constructor)
ctype.Kind() != reflect.Func
for _, o := range opts {
o.applyProvideOption(&options)
}
err := options.Validate();
err := s.provide(constructor, options);
errFunc = digreflect.InspectFunc(constructor)
首先通过反射获取参数的类型,参数必须是构造函数,所以需要判断是否是函数类型。然后修改option参数,校验。执行provide方法,最后检验构造函数的有效性。
func (s *Scope) provide(ctor interface{}, opts provideOptions) (err error)
s = s.rootScope()
allScopes := s.appendSubscopes(nil)
s.gh.Snapshot()
n, err := newConstructorNode()
keys, err := s.findAndValidateResults(n.ResultList())
ctype := reflect.TypeOf(ctor)
oldProviders[k] = s.providers[k]
s.providers[k] = append(s.providers[k], n)
ok, cycle := graph.IsAcyclic(s.gh);
s.providers[k] = ops
s.nodes = append(s.nodes, n)
params := n.ParamList().DotParam()
results := n.ResultList().DotResult()
首先构造node节点,然后根据入参,即构造函数的返回值,得到keys,其实能够唯一确认一种构造函数的返回值类型,其中key的定义如下
type key struct {
t reflect.Type
// Only one of name or group will be set.
name string
group string
}
接着分别把node放入孩子列表中,把依赖构造函数存入providers 这个map中。解析出key的过程如下,通过visitor模式,遍历返回值列表实现的。
func (s *Scope) findAndValidateResults(rl resultList) (map[key]struct{}, error) {
var err error
keyPaths := make(map[key]string)
walkResult(rl, connectionVisitor{
s: s,
err: &err,
keyPaths: keyPaths,
})
3,Invoke执行对象初始化过程
go.uber.org/[email protected]/invoke.go
func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error {
return c.scope.Invoke(function, opts...)
}
func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) error {
ftype := reflect.TypeOf(function)
ftype.Kind() != reflect.Func
err := shallowCheckDependencies(s, pl)
ok, cycle := graph.IsAcyclic(s.gh);
args, err := pl.BuildList(s)
returned := s.invokerFn(reflect.ValueOf(function), args)
}
同样也是获取函数的类型,校验是不是函数。检查依赖是否完整,是否有环。构建函数的参数列表。最后调用invokerFn执行函数。
func shallowCheckDependencies(c containerStore, pl paramList) error
missingDeps := findMissingDependencies(c, pl.Params...)
func findMissingDependencies(c containerStore, params ...param) []paramSingle
switch p := param.(type) {
case paramSingle:
getAllValueProviders
getDecoratedValue
case paramObject:
for _, f := range p.Fields {
missingDeps = append(missingDeps, findMissingDependencies(c, f.Param)...)
根据Invoke传入函数参数列表的类型,如果是简单类型直接解析,如果是对象,根据对象的属性,进行递归解析找到对应的构造函数。
func (s *Scope) getAllValueProviders(name string, t reflect.Type) []provider {
return s.getAllProviders(key{name: name, t: t})
}
func (s *Scope) getAllProviders(k key) []provider {
allScopes := s.ancestors()
var providers []provider
for _, scope := range allScopes {
providers = append(providers, scope.getProviders(k)...)
func (s *Scope) getProviders(k key) []provider {
nodes := s.providers[k]
}
其实就是在我们前面注入的map里面去找依赖的构造函数和装饰函数。
func (s *Scope) getDecoratedValue(name string, t reflect.Type) (v reflect.Value, ok bool) {
v, ok = s.decoratedValues[key{name: name, t: t}]
return
}
其中装饰也是一个接口go.uber.org/[email protected]/decorate.go
func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error {
dn, err := newDecoratorNode(decorator, s)
keys, err := findResultKeys(dn.results)
s.decorators[k] = dn
通过属性注入的方式的相关源码定义在go.uber.org/[email protected]/inout.go
type Out struct{ _ digSentinel }
type In struct{ _ digSentinel }
其实就是一种特殊的类型
type digSentinel struct{}
func IsIn(o interface{}) bool {
return embedsType(o, _inType)
}
_inType = reflect.TypeOf(In{})
func IsOut(o interface{}) bool {
return embedsType(o, _outType)
}
原理其实就是通过反射检查对象的熟悉是否有我们定义的特殊类型In和Out来进行类型的注入和查找的。
func embedsType(i interface{}, e reflect.Type) bool {
t, ok := i.(reflect.Type)
t = reflect.TypeOf(i)
t := types.Remove(types.Front()).(reflect.Type)
f := t.Field(i)
if f.Anonymous {
types.PushBack(f.Type)
4,依赖可视化
如果对象的依赖非常复杂,分析代码有一定难度。可以根据依赖关系生成graphviz格式的依赖关系图。
type A struct{}
type B struct{}
type C struct{}
type D struct{}
func NewD(b *B, c *C) *D {
fmt.Println("NewD()")
return new(D)
}
func NewB(a *A) *B {
fmt.Println("NewB()")
return new(B)
}
func NewC(a *A) *C {
fmt.Println("NewC()")
return new(C)
}
func NewA() *A {
fmt.Println("NewA()")
return new(A)
}
func main() {
// 创建 dig 对象
digObj := dig.New()
// 利用 Provide 注入依赖
digObj.Provide(NewA)
digObj.Provide(NewC)
digObj.Provide(NewB)
digObj.Provide(NewD)
var d *D
assignD := func(argD *D) {
fmt.Println("assignD()")
d = argD
}
fmt.Println("before invoke")
// 根据提前注入的依赖来生成对象
if err := digObj.Invoke(assignD); err != nil {
panic(err)
}
if err := digObj.Invoke(func(a *A, b *B, c *C) {
d = NewD(b, c)
}); err != nil {
panic(err)
}
b := &bytes.Buffer{}
if err := dig.Visualize(digObj, b); err != nil {
panic(err)
}
ioutil.WriteFile("dig.dot", b.Bytes(), fs.ModePerm)
}
生成对应的png格式
% dot -T png dig.dot -o dig.dot.png
推荐阅读