依赖注入的本质是通过分析依赖对象的构造函数的输入输出参数,解析出之间的联系,简化顶层对象的构造过程。
如何实现依赖注入业界有两种知名的方案,一种是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.OutPrimaryDSN *DSN `name:"primary"`SecondaryDSN *DSN `name:"secondary"`}type DBInfo struct {dig.InPrimaryDSN *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 Scopename 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.Funcfor _, 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] = opss.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 stringgroup string}
接着分别把node放入孩子列表中,把依赖构造函数存入providers 这个map中。解析出key的过程如下,通过visitor模式,遍历返回值列表实现的。
func (s *Scope) findAndValidateResults(rl resultList) (map[key]struct{}, error) {var err errorkeyPaths := 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.Funcerr := 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) errormissingDeps := findMissingDependencies(c, pl.Params...)
func findMissingDependencies(c containerStore, params ...param) []paramSingleswitch p := param.(type) {case paramSingle:getAllValueProvidersgetDecoratedValuecase 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 []providerfor _, 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 *DassignD := 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推荐阅读