【Go语言实战】17. inject库


前面已经学习了反射的基本概念和相关的API,这里介绍一个非常著名的包 inject。**inject借助反射提供了对两种类型实体的注入:函数和结构。**Go语言著名Web框架 martini的依赖包注入使用的就是这个包。

1. 依赖注入和控制反转

正常情况下,对函数或方法的调用是调用方的主动直接行为,调用方清楚地知道被调的函数名是什么,参数有哪些类型,直接主动地调用;包括对象的初始化也是显式地直接初始化。所谓的“控制反转”就是将这种主动行为变为间接的行为,主调方不是直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为被称为“控制反转”,控制反转可以解耦调用方和被调方。

  • 一般情况下,使用库的程序是程序主动地调用库的功能,但使用框架的程序常常由框架驱动整个程序,在框架下写的业务代码是被框架驱动的,这种模式就是“控制反转”。
  • “依赖注入”是实现“控制反转”的一种方法,是通过注入的参数或实例的方式实现控制反转。
  • 控制反转的价值在哪里?一句话“解耦”,可以让控制反转的框架代码读取配置,动态地构建对象。
  • 控制反转是解决复杂问题的一种方法,特别是在web框架中为路由和中间件的灵活注入提供了很好的方法。

2. inject

inject是Go语言依赖注入的实现,它实现了对结构(struct)和函数的依赖注入。首先思考如何通过一个字符串类型的函数名调用函数。能想到的方法是使用map实现一个字符串到函数的映射。代码如下:

package main

func f1() {
	println("f1")
}
func f2() {
	println("f2")
}

func main() {
	funcs := make(map[string]func())
	funcs["f1"] = f1
	funcs["f2"] = f2

	funcs["f1"]()
	funcs["f2"]()
}

首先第一个问题是map的Value类型被写成了func(),这个不能适用于不同参数和返回值类型的函数。可以解决的方法是将map的Value定义为interface{}空接口类型,但是需要借助类型断言或反射来实现。inject包借助反射实现函数的注入调用。代码如下:

package main

import (
	"fmt"
	"github.com/codegangsta/inject"
)

type S1 interface{}
type S2 interface{}

func Format(name string, company S1, level S2, age int) {
	fmt.Printf("name=%s, company=%s, level=%s, age=%d!\n", name, company, level, age)
}

func main() {
	//控制实例的创建
	inj := inject.New()
	//实参注入
	inj.Map("Tom")
	inj.MapTo("tencent", (*S1)(nil))
	inj.MapTo("T4", (*S2)(nil))
	inj.Map(23)

	//函数反转调用
	inj.Invoke(Format)

}

inject提供了一种注入参数调用函数的通用功能,inject.New()相当于创建了一个控制实例,由其来实现对函数的注入调用。inject包不但提供了对函数的注入,还实现了对struct类型的注入。例如:

package main

import (
	"fmt"
	"github.com/codegangsta/inject"
)

type S1 interface{}
type S2 interface{}
type Staff struct {
	Name    string `inject`
	Company S1     `inject`
	Level   S2     `inject`
	Age     int    `inject`
}

func main() {
	//创建被注入实例
	s := Staff{}
	//控制实例的创建
	inj := inject.New()
	//初始化注入值
	inj.Map("tom")
	inj.MapTo("tencent", (*S1)(nil))
	inj.MapTo("T4", (*S2)(nil))
	inj.Map(23)
	//实现对 struct 注入
	inj.Apply(&s)
	//打印结果
	fmt.Printf("s = %v\n", s)
}

3. inject实现原理分析

inject包只有 2 个文件,一个是 inject.go 文件和一个 inject_test.go 文件。其中inject.go 短小精悍,包括注释和空行在内才 157 行代码,却提供了一个完美的依赖注入实现。
入口函数New
inject.New()函数构建一个具体类型injector实例作为内部注入引擎,返回的是一个Injector类型的接口。这里体现了一种面向接口的设计思想:对外暴漏接口方法,对内隐藏内部实现。

// New returns a new Injector.
func New() Injector {
	return &injector{
		values: make(map[reflect.Type]reflect.Value),
	}
}

New 方法用于初始化 injector struct,并返回一个指向 injector struct 的指针,但是这个返回值被 Injector 接口包装了。
接口设计
inject.go代码中定义了 4 个接口,包括一个父接口和三个子接口,如下所示:

type Injector interface {
    Applicator //抽象生成注入结构实例的接口
    Invoker //抽象函数调用的接口
    TypeMapper //抽象注入参数的接口
    SetParent(Injector) //实现一个注入实例链,下游的能覆盖上游的类型
}

type Applicator interface {
    Apply(interface{}) error //Appky方法实现对结构的注入
}

type Invoker interface {
    Invoke(interface{}) ([]reflect.Value, error) //Invoke方法是对被注入实参函数的调用
}

type TypeMapper interface {
    // 基于调用reflect.TypeOf得到的类型映射interface{}的值。
    Map(interface{}) TypeMapper
    // 基于提供的接口的指针映射interface{}的值。
    // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
    MapTo(interface{}, interface{}) TypeMapper
    // 为直接插入基于类型和值的map提供一种可能性。
    // 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。
    Set(reflect.Type, reflect.Value) TypeMapper
    // 返回映射到当前类型的Value. 如果Type没被映射,将返回对应的零值。
    Get(reflect.Type) reflect.Value
}

Injector 接口是 Applicator、Invoker、TypeMapper 接口的父接口,所以实现了 Injector 接口的类型,也必然实现了 Applicator、Invoker 和 TypeMapper 接口

  • Applicator 接口只规定了 Apply 成员,它用于注入 struct。
  • Invoker 接口只规定了 Invoke 成员,它用于执行被调用者。
  • TypeMapper 接口规定了三个成员,Map 和 MapTo 都用于注入参数,但它们有不同的用法,Get 用于调用时获取被注入的参数。

另外 Injector 还规定了 SetParent 行为,它用于设置父 Injector,其实它相当于查找继承。也即通过 Get 方法在获取被注入参数时会一直追溯到 parent,这是个递归过程,直到查找到参数或为 nil 终止。

Injector暴漏了所有方法给外部使用者,这些方法又可以归纳为两大类

  • 第一类方法是对参数注入进行初始化,将结构类型的字段的注入和函数的参数注入统一成一套方法实现;
  • 第二类是专用注入实现,分别是生成结构对象和调用函数方法。

注意:无论函数的实参,还是结构的字段,在inject内部,都存放在map[reflecct.Type] reflect.Value类型的map里面。

整个inject包的处理流程如下:

  1. 通过inject.New()创建注入引擎,注入引擎被隐藏,返回的是Injector接口类型变量
  2. 调用TypeMapper接口(Injector内嵌TypeMapper)的方法注入struct的字段值或函数的实参值
  3. 调用Invoker方法执行被注入的函数,或者调用Applicator接口方法获得被注入后的结构实例。

内部实现
首先查看injector的数据结构。injector 是 inject 包中唯一定义的 struct,所有的操作都是基于 injector struct 来进行的,它有两个成员 values 和 parentvalues 用于保存注入的参数,是一个用 reflect.Type 当键、reflect.Value 为值的 map,理解这点将有助于理解 Map 和 MapTo。

type injector struct {
	values map[reflect.Type]reflect.Value
	parent Injector
}

// InterfaceOf dereferences a pointer to an Interface type.
// It panics if value is not an pointer to an interface.
func InterfaceOf(value interface{}) reflect.Type {
	t := reflect.TypeOf(value)

	for t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Interface {
		panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
	}

	return t
}

values里面存放的可以是被注入struct的字段类型和值,也可以是函数实参的类型和值。注意values是以reflect.Type为Key的map,如果一个结构的字段类型相同,则后面注入的参数会覆盖前面的参数,规避的方法是使用MapTo方法,通过抽象出一个接口类型来避免覆盖

InterfaceOf 方法虽然只有几句实现代码,但它是 Injector 的核心。InterfaceOf 方法的参数必须是一个接口类型的指针,如果不是则引发 panic。InterfaceOf 方法的返回类型是 reflect.Type,注意: injector 的成员 values 就是一个 reflect.Type 类型当键的 map。这个方法的作用其实只是获取参数的类型,而不关心它的值。InterfaceOf 方法就是用来得到参数类型,而不关心它具体存储的是什么值。

func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
	i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
	return i
}

injector里面的parent的作用是实现多个注入引擎,其构成了一个链。
injector对函数的注入实现如下:

// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
// It panics if f is not a function
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
	t := reflect.TypeOf(f) //获取函数类型的Type

	// 构造一个存放函数实参Value值的数组
	var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
	//使用反射获取函数实参reflect.Type,逐个去injector中查找注入的Value值
	for i := 0; i < t.NumIn(); i++ {
		argType := t.In(i)
		val := inj.Get(argType)
		if !val.IsValid() {
			return nil, fmt.Errorf("Value not found for type %v", argType)
		}

		in[i] = val
	}

	//反射调用函数
	return reflect.ValueOf(f).Call(in), nil
}

Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。

**Apply 方法是用于对 struct 的字段进行注入,参数为指向底层类型为结构体的指针。**可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),并且此字段的 tag 设置为inject

// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'.
// Returns an error if the injection fails.
func (inj *injector) Apply(val interface{}) error {
	v := reflect.ValueOf(val)

	for v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return nil // Should not panic here ?
	}

	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		f := v.Field(i)
		structField := t.Field(i)
		if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
			ft := f.Type()
			v := inj.Get(ft)
			if !v.IsValid() {
				return fmt.Errorf("Value not found for type %v", ft)
			}

			f.Set(v)
		}

	}

	return nil
}

Map和MapTo方法比较
**Map 和 MapTo 方法都用于注入参数,保存于 injector 的成员 values 中。**这两个方法的功能完全相同,唯一的区别就是 Map 方法用参数值本身的类型当键,而 MapTo 方法有一个额外的参数可以指定特定的类型当键。但是 MapTo 方法的第二个参数 ifacePtr 必须是接口指针类型,因为最终 ifacePtr 会作为 InterfaceOf 方法的参数

// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
// It returns the TypeMapper registered in.
func (i *injector) Map(val interface{}) TypeMapper {
	i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
	return i
}

func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
	i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
	return i
}

// Maps the given reflect.Type to the given reflect.Value and returns
// the Typemapper the mapping has been registered in.
func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper {
	i.values[typ] = val
	return i
}

func (i *injector) Get(t reflect.Type) reflect.Value {
	val := i.values[t]

	if val.IsValid() {
		return val
	}

	// no concrete types found, try to find implementors
	// if t is an interface
	if t.Kind() == reflect.Interface {
		for k, v := range i.values {
			if k.Implements(t) {
				val = v
				break
			}
		}
	}

	// Still no type found, try to look it up on the parent
	if !val.IsValid() && i.parent != nil {
		val = i.parent.Get(t)
	}

	return val

}

func (i *injector) SetParent(parent Injector) {
	i.parent = parent
}

为什么需要有 MapTo 方法?因为注入的参数是存储在一个以类型为键的 map 中,可想而知,当一个函数中有一个以上的参数的类型是一样时,后执行 Map 进行注入的参数将会覆盖前一个通过 Map 注入的参数。

SetParent 方法用于给某个 Injector 指定父 Injector。Get 方法通过 reflect.Type 从 injector 的 values 成员中取出对应的值,它可能会检查是否设置了 parent,直到找到或返回无效的值,最后 Get 方法的返回值会经过 IsValid 方法的校验。

inject对函数注入调用实现很间接,就是从injector里面获取函数实参,然后调用函数。

示例代码如下:

package main

import (
    "fmt"
    "github.com/codegangsta/inject"
)

type SpecialString interface{}

type TestStruct struct {
    Name   string `inject`
    Nick   []byte
    Gender SpecialString `inject`
    uid    int           `inject`
    Age    int           `inject`
}

func main() {
    s := TestStruct{}
    inj := inject.New()
    inj.Map("张三")
    inj.MapTo("男", (*SpecialString)(nil))
    inj2 := inject.New()
    inj2.Map(26)
    inj.SetParent(inj2)
    inj.Apply(&s)
    fmt.Println("s.Name =", s.Name)
    fmt.Println("s.Gender =", s.Gender)
    fmt.Println("s.Age =", s.Age)
}

4. 反射的优缺点

  1. 优点:通用性、灵活性
  2. 缺点:反射是脆弱的、反射是晦涩难懂的、反射有部分性能损失(提供了动态修改程序状态的能力,必然不是直接的地址引用,而是要借助运行时构造一个抽象层)

反射的最佳实践:1. 在库或框架内部使用反射,而不是把反射结构暴漏给调用者;2. 框架代码才考虑使用反射,一般业务没有必要抽象到参设的层次;3. 除非没有其他办法,否则不要使用反射技术。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值