Bootstrap

Go语言——反射

文章目录

一、reflect-反射-浅析-重要

反射是众多编程语言中的一个非常实用的功能,它是一种能够自描述、自控制的应用,Go语言也对反射提供了友好的支持。

Go语言中使用反射可以在编译时不知道类型的情况下更新变量,在运行时查看值、调用方法以及直接对他们的布局进行操作。

由于反射是建立在类型系统(type system)上的,所以我们先来复习一下Go语言中的类型。

Go语言中的类型

Go语言是一门静态类型的语言,每个变量都有一个静态类型,类型在编译的时候确定下来。

type MyInt int

var i int
var j MyInt

变量 i 的类型是 int,变量 j 的类型是 MyInt,虽然它们有着相同的基本类型,但静态类型却不一样,在没有类型转换的情况下,它们之间无法互相赋值。

接口是一个重要的类型,它意味着一个确定的方法集合,一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身),例如 io.Reader 和 io.Writer:

// Reader是包装基本Read方法的接口。
type Reader interface {
  Read(p []byte) (n int, err error)
}

// Writer是包装基本Write方法的接口。
type Writer interface {
  Write(p []byte) (n int, err error)
}

如果一个类型声明实现了 Reader(或 Writer)方法,那么它便实现了 io.Reader(或 io.Writer),这意味着一个 io.Reader 的变量可以持有任何一个实现了 Read 方法的的类型的值。

// 例一:
var r io.Reader
r = os.Stdin
fmt.Printf("%T\n",r) // *os.File
r = bufio.NewReader(r)
fmt.Printf("%T\n",r) // *bufio.Reader
r = new(bytes.Buffer)
fmt.Printf("%T\n",r) // *bytes.Buffer
// 三者都是实现了io.Reader接口的结构体

必须要弄清楚的一点是,不管变量 r 中的具体值是什么,r 的类型永远是 io.Reader,由于Go语言是静态类型的,r 的静态类型就是 io.Reader。

// 例二:
type person interface {
	speak() string
}
type student struct {
	name string
}
func (h student) speak() string {
	return h.name
}
func main() {
	var per person
	fmt.Printf("%T\n",per) // <nil>
	per = student{name: "胡宇洋"}
	fmt.Printf("%T\n",per) // main.student
}

可以看到student 结构体实现了person接口,per的具体值是一个student结构体,但由于GO语言是静态类型的,per的静态类型就是person

如果不知道咋回事(我就不知道)就先略过,这个需要看interface深度解析,里面涉及汇编等东西,在以后有时间可以看一下。

在接口类型中有一个极为重要的例子——空接口:

interface{}

它表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。

有人说Go语言的接口是动态类型,这是错误的,它们都是静态类型,虽然在运行时中,接口变量存储的值也许会变,但接口变量的类型是不会变的。

我们必须精确地了解这些,因为反射与接口是密切相关的。

关于接口我们就介绍到这里,下面我们看看Go语言的反射三定律。


反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”

即:TypeOf(i interface{})ValueOf(i interface{})方法返回反射类型:reflect.Type类型和reflect.Value类型的变量

注:这里反射类型指 reflect.Typereflect.Value。

从使用方法上来讲,反射提供了一种机制,允许程序在运行时检查接口变量内部存储的 (value, type) 对。

在最开始,我们先了解下 reflect 包的两种类型 Type 和 Value,这两种类型使访问接口内的数据成为可能,它们对应两个简单的方法,分别是 reflect.TypeOf 和 reflect.ValueOf,分别用来读取接口变量的 reflect.Type 和 reflect.Value 部分。

当然,从 reflect.Value 也很容易获取到 reflect.Type,目前我们先将它们分开。

首先,我们下看 reflect.TypeOf:

func main() {
    var x float64 = 3.4    
    fmt.Println("type:", reflect.TypeOf(x))
}

运行结果如下

type: float64

大家可能会疑惑,为什么没看到接口?

这段代码看起来只是把一个 float64 类型的变量 x 传递给 reflect.TypeOf 并没有传递接口。

其实在 reflect.TypeOf 的函数签名里包含一个空接口:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

我们调用 reflect.TypeOf(x) 时,x 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。

函数 reflect.ValueOf 也会对底层的值进行恢复:

func main() {
    var x float64 = 3.4    
    fmt.Println("value:", reflect.ValueOf(x))
}

运行结果如下:

value: 3.4

类型reflect.Typereflect.Value都有很多方法,我们可以检查和使用它们,这里我们举几个例子。

类型reflect.Value有一个方法Type(),它会返回一个reflect.Type 类型的对象。

Type 和 Value 都有一个名为Kind ()的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice 等。

Value 类型也有一些类似于 Int、Float 的方法,用来提取底层的数据:

  • Int 方法用来提取 int64
  • Float 方法用来提取 float64,示例代码如下:
func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x) // 返回一个reflect.Value 类型的对象
	fmt.Println("type:", v.Type()) // v.Type返回一个reflect.Type类型的对象
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)// kind()返回一个常量,表示底层数据类型
	fmt.Println("value:", v.Float()) // 提取底层的数据
}

运行结果如下:

type: float64
kind is float64: true
value: 3.4

还有一些用来修改数据的方法,比如 SetInt、SetFloat。

在介绍它们之前,我们要先理解“可修性”(settability),这一特性会在下面进行详细说明。

反射库提供了很多值得列出来单独讨论的属性,下面就来介绍一下。

首先是介绍下 reflect.Value类型 的 getter 和 setter 方法,为了保证 API 的精简,这两个方法操作的是某一组类型范围最大的那个。比如,处理任何含符号整型数,都使用 int64,也就是说 Value 类型的 Int 方法返回值为 int64 类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型:

func main() {    
    var x uint8 = 'x'    
    v := reflect.ValueOf(x)    
    fmt.Println("type:", v.Type())  // uint8. 
    fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.    
    x = uint8(v.Uint())  // v.Uint()返回的是uint64类型.
}

运行结果如下:

type: uint8
kind is uint8: true

其次,反射对象的 Kind() 方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下所示:

func main() {
	type MyInt int
	var x MyInt = 7
	v := reflect.ValueOf(x)
	fmt.Println(v.Kind()) // int
	fmt.Println(v.Type()) // main.MyInt
}

上面的代码中,虽然变量 v 的静态类型是 MyInt,而不是 int,但 Kind 方法仍然会返回 reflect.Int。

换句话说 Kind ()方法不会像 Type() 方法一样区分 MyInt 和 int。


反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”

和物理学中的反射类似,Go语言中的反射也能创造自己反面类型的对象。

根据一个reflect.Value类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。

其函数声明如下:

// Interface returns v's value as an interface{}.
// 相当于:var i interface{}=(v的底层值)。
func (v Value) Interface() interface{}

然后,我们可以通过断言,恢复底层的具体值:

func main() {
	var x float64  = 12.1
	v:=reflect.ValueOf(x)
	y,ok:= v.Interface().(float64) // y will have type float64.
	fmt.Println(ok,y)// true 12.1
}

上面这段代码会打印出一个 float64 类型的值,也就是反射类型变量 v 所代表的值。

事实上,我们可以更好地利用这一特性,标准库中的 fmt.Println 和 fmt.Printf 等函数都接收空接口变量作为参数,fmt 包内部会对接口变量进行拆包,因此 fmt 包的打印函数在打印 reflect.Value 类型变量的数据时,只需要把 Interface 方法的结果传给格式化打印程序:

fmt.Println(v.Interface())

那么为什么不直接使用 fmt.Println(v)?

因为 v 的类型是 reflect.Value,我们需要的是它的具体值,由于值的类型是 float64,我们也可以用浮点格式化打印它:

fmt.Printf("value is %7.1e\n", v.Interface())

运行结果如下:

3.4e+00

同样,这次也不需要对 v.Interface() 的结果进行类型断言,空接口值内部包含了具体值的类型信息,Printf 函数会恢复类型信息。

func main() {
	var x float64  = 12.1
	v:=reflect.ValueOf(x)
	fmt.Printf("%T\n",v) // reflect.Value
	fmt.Printf("%T\n",v.Interface()) // float64
}

简单来说 Interface 方法和 ValueOf 函数作用恰好相反,唯一一点是,返回值的静态类型是 interface{}。

Go的反射机制可以将“接口类型的变量”转换为“反射类型的对象”,然后再将“反射类型对象”转换过去。

反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”

这条定律很微妙,也很容易让人迷惑,但是如果从第一条定律开始看,应该比较容易理解。

下面这段代码虽然不能正常工作,但是非常值得研究:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic

如果运行这段代码,它会抛出一个奇怪的异常:

panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

这里问题不在于值7.1不能被寻址,而是因为变量 v 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。

我们可以通过 CanSet() 方法检查一个 reflect.Value 类型变量的“可写性”,对于上面的例子,可以这样写:

func main() {
	var x float64  = 12.1
	v:=reflect.ValueOf(x)
	fmt.Println("settability of v:", v.CanSet())
}

运行结果如下:

settability of v: false

对于一个不具有“可写性”的 Value 类型变量,调用 Set 方法会报出错误。

首先我们要弄清楚什么是“可写性”,“可写性”有些类似于寻址能力,但是更严格,它是反射类型变量的一种属性,赋予该变量修改底层存储数据的能力。“可写性”最终是由一个反射对象是否存储了原始值而决定的。

示例代码如下:

var x float64 = 3.4
v := reflect.ValueOf(x)

这里我们传递给 reflect.ValueOf 函数的是变量 x 的一个拷贝,而非 x 本身,想象一下如果下面这行代码能够成功执行:

v.SetFloat(7.1)

如果这行代码能够成功执行,它不会更新 x,虽然看起来变量 v 是根据 x 创建的,相反它会更新 x 存在于反射对象 v 内部的一个拷贝,而变量 x 本身完全不受影响。这会造成迷惑,并且没有任何意义,所以是不合法的。“可写性”就是为了避免这个问题而设计的。

这看起来很诡异,事实上并非如此,而且类似的情况很常见。考虑下面这行代码:

f(x)

代码中,我们把变量 x 的一个拷贝传递给函数,因此不期望它会改变 x 的值。如果期望函数 f 能够修改变量 x,我们必须传递 x 的地址(即指向 x 的指针)给函数 f,如下所示:

f(&x)

反射的工作机制与此相同,如果想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库。

首先,像通常一样初始化变量 x,然后创建一个指向它的反射对象,命名为 p:

func main() {
	var x float64 = 3.4
	p := reflect.ValueOf(&x) // Note: take the address of x.
	fmt.Println("type of p:", p.Type())
	fmt.Println("settability of p:", p.CanSet())
}

运行结果如下:

type of p: *float64
settability of p: false

反射对象 p 是不可写的,但是我们也不想修改 p,事实上我们要修改的是 *p。为了得到 p 指向的数据,可以调用 Value 类型的 Elem 方法。Elem 方法能够对指针进行“解引用”,然后将结果存储到反射 Value 类型对象 v 中:

func main() {    
    var x float64 = 3.4    
    p := reflect.ValueOf(&x) // Note: take the address of x.    
    v := p.Elem()    
    fmt.Println("settability of v:", v.CanSet())
}

运行结果如下:

settability of v: true

由于变量 v 代表 x, 因此我们可以使用 v.SetFloat 修改 x 的值:

func main() {
	var x float64 = 3.4
	p := reflect.ValueOf(&x) // Note: take the address of x.
	v := p.Elem() // 获取指针&x指向的变量x,等同于*(&x),但是在反射中不可以那么写,有一个专门的方法Elem()获取指针指向的变量
	v.SetFloat(7.1)
	fmt.Println(v.Interface())
	fmt.Println(x)
}

运行结果如下:

7.1
7.1

Elem():专门用来获取指针指向的变量,返回仍然还是一个reflect.Vlaue类型

反射不太容易理解,reflect.Type 和 reflect.Value 会混淆正在执行的程序,但是它做的事情正是编程语言做的事情。只需要记住:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

结构体

我们一般使用反射修改结构体的字段,只要有结构体的指针,我们就可以修改它的字段。

下面是一个解析结构体变量 t 的例子,用结构体的地址创建反射变量,再修改它。然后我们对它的类型设置了 typeOfT,并用调用简单的方法迭代字段。

需要注意的是,我们从结构体的类型中提取了字段的名字,但每个字段本身是正常的 reflect.Value 对象。

func main() {
	type Student struct {
		Age int
		Name string
	}
	t := Student{19, "hyy"}
	s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
	typeOfT := s.Type()
	fmt.Println(typeOfT) // main.Student
	// 迭代字段
	// s.NumField() 返回结构体的字段个数
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i) // 取第i个字段
		fmt.Printf(
			"字段名:%s 字段类型:%s = %v\n",
			typeOfT.Field(i).Name, // 获取字段名
			f.Type(), // 获取类型
			f.Interface(), // 获取值
		)
	}
}

运行结果如下:

main.Student
字段名:Age 字段类型:int = 19
字段名:Name 字段类型:string = hyy

Student 字段名之所以大写,是因为结构体中只有可导出的字段是“可设置”的。

// 如果设置成
type Student struct {
    Age int
    name string
}

会报错painc:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRWg4MXw-1642873066072)(images/image-20220117204127012.png)]

因为 s 包含了一个可设置的反射对象,我们可以修改结构体字段:

func main() {
	type Student struct {
		Age int
		Name string
	}
	t := Student{19, "hyy"}
	s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
	s.Field(0).SetInt(123) // t.Age=123
	s.Field(1).SetString("123") // t.Name="123"
	fmt.Println("修改后:",t)
}

运行结果如下:

修改后: {123 123}

如果我们修改了程序让 s 由 t(而不是 &t)创建,程序就会在调用 SetInt 和 SetString 的地方失败,因为 t 的字段是不可设置的。

总结

反射规则可以总结为如下几条:

  • 反射可以将“接口类型变量”转换为“反射类型对象”;
  • 反射可以将“反射类型对象”转换为“接口类型变量”;
  • 如果要修改“反射类型对象”,其值必须是“可写的”。

二、reflect-反射类型对象-获取类型信息

1. TypeOf()和reflect.Type类型

通过反射获取类型信息

在 Go语言中通过调用 reflect.TypeOf() 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。

reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。

当然,我们也可以将一个接口值传递给一个 reflect.TypeOf() 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。

实际上,reflect.TypeOf() 函数的唯一参数的类型为 interface{},reflect.TypeOf() 函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type值。

那如何得到一个表示着某个接口类型的 reflect.Type 值呢?

我们必须通过下面将要介绍的一些间接途径来达到这一目的。

类型 reflect.Type 为一个接口类型,它指定了若干方法(Type)

通过这些方法,我们能够观察到一个 reflect.Type 值所表示的 Go类型的各种信息。这些方法中的有的适用于所有种类(Kind)的类型,有的只适用于一种或几种类型。

通过不合适的 reflect.Type 值调用某个方法将在运行时产生一个painc。

使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:

func main() {    
    var a int
    // 通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()。
    typeOfA := reflect.TypeOf(a)
    // 通过 typeOfA 类型对象的成员函数,
    // 可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 int。
    fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

int  int

1.1 理解反射的类型(Type)与种类(Kind)

在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。

编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。

例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。

总结:

type Student struct{...}

type 类型是Student

kind 种类是struct

1.1.1 反射种类(Kind)的定义

Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。

例如使用 type A struct{}定义结构体时,A 就是 struct{} 的类型。

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

type Kind uint
const (    
    Invalid Kind = iota  // 非法类型    
    Bool                 // 布尔型    
    Int                  // 有符号整型    
    Int8                 // 有符号8位整型    
    Int16                // 有符号16位整型    
    Int32                // 有符号32位整型    
    Int64                // 有符号64位整型    
    Uint                 // 无符号整型    
    Uint8                // 无符号8位整型    
    Uint16               // 无符号16位整型    
    Uint32               // 无符号32位整型    
    Uint64               // 无符号64位整型    
    Uintptr              // 指针    
    Float32              // 单精度浮点数    
    Float64              // 双精度浮点数    
    Complex64            // 64位复数类型    
    Complex128           // 128位复数类型    
    Array                // 数组    
    Chan                 // 通道    
    Func                 // 函数    
    Interface            // 接口    
    Map                  // 映射    
    Ptr                  // 指针    
    Slice                // 切片    
    String               // 字符串    
    Struct               // 结构体    
    UnsafePointer        // 底层指针
)
  1. Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。

  2. type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

1.1.2 从类型对象中获取类型名称和种类的例子

Go语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。

类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。

下面的代码中会对常量和结构体进行类型信息获取。

// Enum 定义一个Enum类型
type Enum int
const (
	Zero Enum = 0
)
func main() {
	// 声明一个空结构体
	type cat struct{}
	// 获取结构体实例的反射类型对象
	typeOfCat := reflect.TypeOf(cat{})
	// 显示反射类型对象的名称和种类
	fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
	// 获取Zero常量的反射类型对象
	typeOfA := reflect.TypeOf(Zero)
	// 显示反射类型对象的名称和种类
	fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

cat struct
Enum int

1.2. reflect.Elem()

通过反射获取指针指向的元素类型。

func main() {
    // 声明一个空结构体
    type cat struct {
    }
    // 创建cat的实例
    ins := cat{}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(&ins)
    // 显示反射类型对象的名称和种类
    fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
    // 取类型的元素
    typeOfCat = typeOfCat.Elem()
    // 显示反射类型对象的名称和种类
    fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}

对于指针一般都是直接

typeOfX := reflect.TypeOf(&x).Elem

取指针指向元素类型

1.3. 通过反射类型对象获取结构体的成员类型

任意值通过 reflect.TypeOf() 获得反射类型对象后,

如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。

与成员获取相关的 reflect.Type 的方法如下表所示。

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
当值不是结构体或索引超界时发生panic
NumField() int返回结构体成员字段数量。
当类型不是结构体或索引超界时发生panic
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生panic
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生panic
FieldByNameFunc( match func(string) bool) (StructField,bool)根据匹配函数匹配需要的字段。
当值不是结构体或索引超界时发生panic
1.3.1 结构体字段类型(StructField )

reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。

StructField 的结构如下:

type StructField struct {    
    Name string          // 字段名    
    PkgPath string       // 字段路径    
    Type      Type       // 字段反射类型对象    
    Tag       StructTag  // 字段的结构体标签    
    Offset    uintptr    // 字段在结构体中的相对偏移    
    Index     []int      // Type.FieldByIndex中的返回的索引值    
    Anonymous bool       // 是否为匿名字段
}

字段说明如下。

  • Name:为字段名称。
  • PkgPath:字段在结构体中的路径。
  • Type:字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息。
  • Tag:结构体标签,为结构体字段标签的额外信息,可以单独提取。
  • Index:FieldByIndex 中的索引顺序。
  • Anonymous:表示该字段是否为匿名字段。
func main() {
// 声明一个结构体
type cat struct {
   // 带有结构体tag的字段
   Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(&ins).Elem()
// 2. 通过字段名, 找到字段类型信息
catType, ok := typeOfCat.FieldByName("Type")
if ok {
   fmt.Println(catType.Name) //Type
   fmt.Println(catType.PkgPath) // ""
   fmt.Println(catType.Type) // int
   fmt.Println(catType.Tag) // json:"type" id:"100"
   fmt.Println(catType.Offset) // 16
   fmt.Println(catType.Index) // [1]
   fmt.Println(catType.Anonymous) // false
}
}
1.3.2 获取成员反射信息

下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。

反射访问结构体成员类型及信息:

func main() {
	// 声明一个空结构体
	type cat struct {
		Name string
		// 带有结构体tag的字段
		Type int `json:"type" id:"100"`
	}
	// 创建cat的实例
	ins := cat{Name: "mimi", Type: 1}
	// 获取结构体实例的反射类型对象
	typeOfCat := reflect.TypeOf(&ins).Elem()
	// 方法1. 遍历结构体所有成员字段
	for i := 0; i < typeOfCat.NumField(); i++ {
		// 获取每个成员的结构体字段类型
		fieldType := typeOfCat.Field(i)
		// 输出成员名,成员反射类型和tag
		fmt.Printf("name: %v,type: %v,tag: '%v'\n", fieldType.Name,fieldType.Type, fieldType.Tag)
	}
	// 方法2. 通过字段名, 找到字段类型信息
	catType, ok := typeOfCat.FieldByName("Type")
	if ok {
		// 从tag中取出需要的tag
		fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
	}
}

代码输出如下:

name: Name,type: string,tag: ''
name: Type,type: int,tag: 'json:"type" id:"100"'
type 100

1.4 通过反射类型对象创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。

例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int,代码如下:

func main() {
    var a int
    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)
    // 根据反射类型对象创建类型实例
    // 使用 reflect.New() 函数传入变量 a 的反射类型对象,创建这个类型的实例值,值以 reflect.Value 类型返回。这步操作等效于:new(int),因此返回的是 *int 类型的实例。
    aIns := reflect.New(typeOfA)
    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}

运行结果:

*int ptr

reflect-反射值对象-获取值信息

2. reflect.ValueOf()和reflect.Value类型

通过反射获取值信息

当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。

reflect.Value 类型有很多方法(https://golang.google.cn/pkg/reflect/)。我们可以调用这些方法来观察和操纵一个 reflect.Value 属主值表示的 值。这些方法中的有些适用于所有种类类型的值,有些只适用于一种或几种类型的值。

通过不合适的 reflect.Value 属主值调用某个方法将在运行时产生一个painc。

请阅读 reflect 代码库 中各个方法的文档来获取如何正确地使用这些方法。

一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被赋值)。

如果一个 Go 值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go 值。

注意:reflect.ValueOf(&v) 函数直接返回的 reflect.Value 值都是不可修改的。

.Elem() 函数后返回的(虽然也是reflect.Value类型)才可以修改。

反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。

Go语言中使用 reflect.Value 获取和设置变量的值。

2.1 使用反射值对象<包装>任意值

Go语言中,使用 reflect.ValueOf()函数获得反射值对象(reflect.Value)。

书写格式如下:

valueOfR := reflect.ValueOf(rawValue)

reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。

reflect.Value 与原值间可以通过值包装值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。

2.2 从反射值对象<获取>被包装的值

Go语言中可以通过 reflect.Value 重新获得原始值。

2.2.1 从反射值对象(reflect.Value)中获取值的方法

可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。

方法名说 明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
val,ok := valueOfS.Interface().(Type)
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
2.2.2 从反射值对象(reflect.Value)中获取值的例子

下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象(reflect.Value)。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。

func main() {
	// 声明整型变量a并赋初值
	var a int = 1024
	// 获取指针变量&a的反射值对象,并取元素
	valueOfA := reflect.ValueOf(&a).Elem()
	// 方法1. 获取interface{}类型的值, 通过类型断言转换
	var getA int = valueOfA.Interface().(int)
	// 方法2. 获取64位的值, 强制类型转换为int类型
	var getA2 int = int(valueOfA.Int())
	fmt.Printf("%T\t%T\n",getA, getA2)
}

代码输出如下:

int	int

2.3 通过反射值对象访问结构体成员的值

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
当值不是结构体或索引超界时发生panic
NumField() int返回结构体成员字段数量。
当类型不是结构体或索引超界时发生panic
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生panic
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生panic
FieldByNameFunc( match func(string) bool) (StructField,bool)根据匹配函数匹配需要的字段。
当值不是结构体或索引超界时发生panic

简单使用:

// 定义结构体
type dummy struct {
	a int
	b string
	float32
	bool
	next *dummy // 嵌入字段
}

func main() {
	dum := dummy{
		next: &dummy{},
	}
	// 值包装结构体
	valueOfDum := reflect.ValueOf(&dum).Elem()
	// 获取字段数量
	fmt.Println("NumField(): ", valueOfDum.NumField())
	// 获取索引为2的字段(float32字段 匿名字段)
	floatField := valueOfDum.Field(2)
	// 输出字段类型
	fmt.Println("Field(2): ", floatField.Type())
	// 根据名字查找字段
	fmt.Println("FieldByName(\"b\").Type(): ", valueOfDum.FieldByName("b").Type())
	// 根据索引查找值中, next字段的int字段的值
	fmt.Println("FieldByIndex([]int{4}).Type(): ", valueOfDum.FieldByIndex([]int{4}).Type())
	fmt.Println("FieldByIndex([]int{4, 1}).Type(): ", valueOfDum.FieldByIndex([]int{4,1}).Type())
}


运行结果:

NumField():  5
Field(2):  float32
FieldByName("b").Type():  string
FieldByIndex([]int{4}).Type():  *main.dummy
FieldByIndex([]int{4, 0}).Type():  string

例二:

type student struct {
	Name string
	Age int
}
func main() {
	stu:=student{
		Name: "Hyy",
		Age: 19,
	}
	ValueOfStu := reflect.ValueOf(&stu).Elem()
	ValueOfStu.FieldByName("Name").SetString("胡宇洋")
	ValueOfStu.FieldByName("Age").SetInt(12)
	fmt.Println(stu)
}

运行结果:

{胡宇洋 12}

2.4 判断反射值对象的空和有效性

2.4.1 reflect.IsNil()和reflect.IsValid()

反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如下表所示。

方 法说 明
IsNil() bool返回值是否为 nil。
如果值类型不是通道(channel)、函数接口map指针切片 时发生panic,类似于语言层的v == nil操作
IsValid() bool判断值是否有效。
当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。

下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。

同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定,参考下面的代码。

反射值对象的零值和有效性判断:

func main() {
	// *int的空指针
	var a *int
	fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
	// nil值
	fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
	// *int类型的空指针
	fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
	// 实例化一个结构体
	s := struct{}{}
	// 尝试从结构体中查找一个不存在的字段
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
	// 尝试从结构体中查找一个不存在的方法
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
	// 实例化一个map
	m := map[int]int{}
	// 尝试从map中查找一个不存在的键
	fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

注意:

IsValid():对于任何值都调用,判断值的有效性。

IsNil():仅对于接口、map、指针、切片、通道、函数类型可用,其他类型调用 ,panic

2.5通过反射值对象修改变量的值

一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

对于 reflect.Values 也有类似的区别。

有一些 reflect.Values 是可取地址的;其它一些则不可以。

考虑以下的声明语句:

x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := reflect.ValueOf(&x).Elem() // 2 int yes (x)

其中 a 对应的变量则不可取地址,因为 a 中的值仅仅是整数 2 的拷贝副本。

b 中的值也同样不可取地址。

c 中的值还是不可取地址,它只是一个指针 &x 的拷贝。

实际上,所有通过 reflect.ValueOf(x) 直接返回的 reflect.Value 都是不可取地址的。

但是对于 d,它是 c 的解引用方式生成的,指向另一个变量,因此是可取地址的。

我们可以通过调用 reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的 Value。

我们可以通过调用 reflect.Value 的 CanAddr()方法来判断其是否可以被取地址:

fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"

每当我们通过指针间接地获取的 reflect.Value 都是可取地址的,即使开始的是一个不可取地址的 Value。

在反射机制中,所有关于是否支持取地址的规则都是类似的。

func main() {
	y:=[]int{1,2,3}
	e:= reflect.ValueOf(y)
	fmt.Println(e.CanAddr()) //false
	fmt.Println(e.Index(1).CanAddr()) // true
}

例如,slice 的索引表达式 e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。

以此类推,reflect.ValueOf(e).Index(i) 对于的值也是可取地址的,即使原始的 reflect.ValueOf(e) 不支持也没有关系。

使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。

如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。

判定及获取元素的相关方法

使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

方法名备 注
Elem() Value取反射值对象指向的元素值,类似于语言层*操作。
当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value
Addr() Value对可寻址的值返回其地址,类似于语言层&操作。
当值不可寻址时发生宕机
CanAddr() bool表示值是否可寻址
CanSet() bool返回值能否被修改。
要求值可寻址且是导出(即首字母大写)的字段
反射值对象的值修改相关方法

使用 reflect.Value 修改值的相关方法如下表所示。

Set(x Value)将值设置为传入的反射值对象的值
Setlnt(x int64)使用 int64 设置值。
当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64)使用 uint64 设置值。
当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64)使用 float64 设置值。
当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool)使用 bool 设置值。
当值的类型不是 bool 时会发生宕机
SetBytes(x []byte)设置字节数组 []bytes值。
当值的类型不是 []byte 时会发生宕机
SetString(x string)设置字符串值。
当值的类型不是 string 时会发生宕机

以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。

在已知值的类型时,应尽量使用值对应类型的反射设置值。

值可修改条件之一:可被寻址

通过反射修改变量值的前提条件之一:这个值必须可以被寻址。

简单地说就是这个变量必须能被修改。

示例代码如下:

func main() {
    var a int = 1024
    valueOfA := reflect.ValueOf(a)
    // 尝试将a修改为1(此处会发生崩溃)
    valueOfA.SetInt(1)
}

程序运行崩溃,打印错误:

panic: reflect: reflect.Value.SetInt using unaddressable value

报错意思是:SetInt 正在使用一个不能被寻址的值。从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下,重新运行:

func main() {
	var a int = 1024
	valueOfA := reflect.ValueOf(&a).Elem()
	valueOfA.SetInt(1)
}

注意:

当 reflect.Value 不可寻址时,使用 Addr()方法也是无法取到值的地址的,同时会发生宕机。

虽然说 reflect.Value 的Addr() 方法类似于语言层的&操作;Elem()方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。

值可修改条件之一:被导出

结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:

type student struct {
	name string
	Age  int
}
func main() {
	stu := student{"hyy", 19}
	valueOfStu := reflect.ValueOf(&stu).Elem()
	valueOfStuAge:=valueOfStu.FieldByName("Age")
	valueOfStuAge.SetInt(20)
	fmt.Println(stu)
	valueOfStuName:=valueOfStu.FieldByName("name")
	valueOfStuName.SetString("hyy2")
}

运行结果:

{hyy 20}
panic: reflect: reflect.Value.SetString using value obtained using unexported field

报错的意思是:SetString() 使用的值来自于一个未导出的字段。

反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 反射值对象 调用该函数。

使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。
将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

func add(a int, b int) int {
	return a+b
}
func main() {
	funcValue:=reflect.ValueOf(add)
	realParameter:= []reflect.Value{reflect.ValueOf(10),reflect.ValueOf(12)}
	resList:=funcValue.Call(realParameter)
	fmt.Println(resList[0].Int())
}

提示

反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。

调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。

因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。

inject库:依赖注入

在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。

正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。

所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。

依赖注入是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的一种实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。

控制反转的价值在于解耦,有了控制反转就不需要将代码写死,可以让控制反转的的框架代码读取配置,动态的构建对象,这一点在 JavaSpring 框架中体现的尤为突出。

inject 实践

inject 是依赖注入的Go语言实现,它能在运行时注入参数,调用方法,是 Martini 框架(Go语言中著名的 Web 框架)的基础核心。

在介绍具体实现之前,先来想一个问题,如何通过一个字符串类型的函数名来调用函数?

Go语言没有 Java 中的 Class.forName 方法可以通过类名直接构造对象,所以这种方法是行不通的,能想到的方法就是使用 map 实现一个字符串到函数的映射,示例代码如下:

func fl() {
    println ("fl")
}
func f2 () {
    println ("f2")
}
funcs := make(map[string] func ())
funcs ["fl"] = fl
funcs ["f2"] = fl
funcs ["fl"]()
funcs ["f2"]()

但是这有个缺陷,就是 map 的 Value 类型被写成 func(),不同参数和返回值的类型的函数并不能通用。

将 map 的 Value 定义为 interface{} 空接口类型即可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。

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()
	// 实参注入
	// 基于调用reflect.TypeOf得到的类型映射interface{}的值。
	inj.Map("tom")
	// 基于提供的接口的指针,映射interface{}的值。
    // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
	inj.MapTo("tencent", (*S1)(nil))
	inj.MapTo("T4", (*S2)(nil))
	inj.Map(23)
	// 函数反转调用
    // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
    // 它将返回reflect.Value的切片,其中存放原函数的返回值。
    // 如果注入失败则返回error.
	inj.Invoke(Format)
}

运行结果:

name = tom, company=tencent, level=T4, age = 23!

可见 inject 提供了一种注入参数调用函数的通用功能,inject.New() 相当于创建了一个控制实例,由其来实现对函数的注入调用。

inject 包不但提供了对函数的注入,还实现了对 struct 类型的注入,示例代码如下所示:

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 注入
   // 在Type map中维持对结构体中每个域的引用,并用\'inject\'来标记
   // 如果注入失败将会返回一个error.
   inj.Apply(&s)
   //打印结果
   fmt.Printf("s = %v\n", s)
}

运行结果:

s = {tom tencent T4 23}

可以看到 inject 提供了一种对结构类型的通用注入方法。

至此,我们仅仅从宏观层面了解 iniect 能做什么,下面从源码实现角度来分析 inject。

inject 原理分析

概述

inject 包中只有 2 个文件,一个是 inject.go 文件和一个 inject_test.go 文件,这里我们只需要关注 inject.go 文件即可。

inject.go 短小精悍,包括注释和空行在内才 157 行代码,代码中定义了 4 个接口,包括一个父接口和三个子接口,如下所示:

type Injector interface {
    Applicator
    Invoker
    TypeMapper
    SetParent(Injector)
}
type Applicator interface {
    Apply(interface{}) error
}
type Invoker interface {
    Invoke(interface{}) ([]reflect.Value, error)
}
type TypeMapper interface {
    Map(interface{}) TypeMapper
    MapTo(interface{}, interface{}) TypeMapper
    Set(reflect.Type, reflect.Value) TypeMapper
    Get(reflect.Type) reflect.Value
}
  1. Injector 接口是 ApplicatorInvokerTypeMapper 接口的父接口,所以实现了 Injector 接口的类型,也必然实现了 Applicator、Invoker 和 TypeMapper 接口:

    • Applicator 接口只规定了 Apply 成员,它用于注入 struct。

    • Invoker 接口只规定了 Invoke 成员,它用于执行被调用者。

    • TypeMapper 接口规定了三个成员,MapMapTo 都用于注入参数,但它们有不同的用法,Get 用于调用时获取被注入的参数。

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


injector,InterfaceOf(),New()

type injector struct {
    values map[reflect.Type]reflect.Value
    parent Injector
}
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
}
func New() Injector {
    return &injector{
        values: make(map[reflect.Type]reflect.Value),
    }
}
  1. injector 是 inject 包中唯一定义的 struct,所有的操作都是基于 injector struct 来进行的,它有两个成员 values 和 parent。
    values 用于保存注入的参数,是一个用 reflect.Type 当键、reflect.Value 为值的 map,理解这点将有助于理解 MapMapTo

  2. New 方法用于初始化 injector struct,并返回一个指向 injector struct 的指针,但是这个返回值被 Injector 接口包装了。

  3. InterfaceOf 方法虽然只有几句实现代码,但它是 Injector 的核心。

    InterfaceOf 方法的参数必须是一个接口类型的指针,如果不是则引发 panic。

    InterfaceOf 方法的返回类型是 reflect.Type,大家应该还记得 injector 的成员 values 就是一个 reflect.Type 类型当键的 map。

    这个方法的作用其实只是获取参数的类型,而不关心它的值。

    示例:

    type SpecialString interface{}
    func main() {
    	fmt.Println(inject.InterfaceOf((*interface{})(nil)))
    	fmt.Println(inject.InterfaceOf((*SpecialString)(nil)))
    }
    

    运行结果:

    interface {}
    main.SpecialString
    

    InterfaceOf 方法就是用来得到参数类型,而不关心它具体存储的是什么值。


Map(),MapTo(),Get(),SetParent()

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
}
func (i *injector) Get(t reflect.Type) reflect.Value {
    val := i.values[t]
    if !val.IsValid() && i.parent != nil {
        val = i.parent.Get(t)
    }
    return val
}
func (i *injector) SetParent(parent Injector) {
    i.parent = parent
}
  1. MapMapTo 方法都用于注入参数,保存于 injector 的成员 values 中。

    这两个方法的功能完全相同,唯一的区别就是 Map 方法用参数值本身的类型当键,而 MapTo 方法有一个额外的参数可以指定特定的类型当键。但是 MapTo 方法的第二个参数 ifacePtr 必须是接口指针类型,因为最终 ifacePtr 会作为 InterfaceOf 方法的参数。

    为什么需要有 MapTo 方法?

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

  2. SetParent 方法用于给某个 Injector 指定父 Injector。

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

示例代码如下所示:

type SpecialString interface{}
func main() {
	inj := inject.New()
	inj.Map("傻子") // map[string]="傻子"
	inj.MapTo("你呀", (*SpecialString)(nil)) // map[SpecialString]="你呀"
	inj.Map(20) // map[int]=20
	// 相当于查找键为string的map键值对
	fmt.Println("字符串是否有效?", inj.Get(reflect.TypeOf("hyy")).IsValid())
	// 查找map[SpecialString]
	fmt.Println("特殊字符串是否有效?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid())
	// map[int]
	fmt.Println("int 是否有效?", inj.Get(reflect.TypeOf(18)).IsValid())
	// map[[]byte]
	fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())

	inj2 := inject.New()
	// map2[[]byte]=[]byte("test")
	inj2.Map([]byte("test"))
	// 设置到父inject中,如果在子中查不到,自动去父中查,父中再找不到,error
	inj.SetParent(inj2)
	fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
    // 从map中取元素
    fmt.Println(string(inj.Get(reflect.TypeOf([]byte("12"))).Bytes())) // test
}

运行结果:

字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
test

通过以上例子应该知道 SetParent 是什么样的行为,是不是很像面向对象中的查找链?


Invoke()

func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
    // 获取参数f的反射类型对象
    t := reflect.TypeOf(f)
    // 准备实参,根据参数 函数f 需要的参数数量创建切片
    var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
    // 遍历获取所需参数类型,根据类型去values中获取对应键值对,获取不到就报错,获取到就并入实参切片。
    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
    }
    // 根据实参切片作为参数调用函数f
    return reflect.ValueOf(f).Call(in), nil
}

Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。

NumIn()
NumIn 返回一个函数类型的输入参数计数。如果类型的 Kind 不是 Func,它会恐慌。

In()
In 返回函数类型的第 i 个输入参数的类型。
如果类型的 Kind 不是 Func,它会恐慌。
如果 i 不在 [0, NumIn()) 范围内,它会恐慌。

Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。

例子:

type SpecialString interface{}
func Say(name string, gender SpecialString, age int) {
	fmt.Printf("My name is %s, gender is %s, age is %d!\n", name, gender, age)
}
func main() {
	inj := inject.New()
	// map[string]="张三"
	inj.Map("张三")
	// map[SpecialString]="男"
	inj.MapTo("男", (*SpecialString)(nil))
	inj2 := inject.New()
	// map2[int]=25
	inj2.Map(25)
	inj.SetParent(inj2)
	inj.Invoke(Say)
}

运行结果:

My name is 张三, gender is 男, age is 25!

上面的例子如果没有定义 SpecialString 接口作为 gender 参数的类型,而把 name 和 gender 都定义为 string 类型,那么 gender 会覆盖 name 的值。


Apply()

func (inj *injector) Apply(val interface{}) error {
    // 获取参数的反射值对象
    v := reflect.ValueOf(val)
    // 如果你传入的参数的种类是指针类型,就取元素
    for v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    // 如果传入的参数的种类不是结构体,返回nil
    if v.Kind() != reflect.Struct {
        return nil
    }
    // 获取反射类型对象
    t := v.Type()
    // 遍历字段域
    for i := 0; i < v.NumField(); i++ {
        // 选择 反射值对象字段域
        f := v.Field(i)
        // 选择 反射类型对象字段域
        structField := t.Field(i)
        // 判断当前反射值对象是否可修改,且当前反射类型对象的标签是否为'inject'
        if f.CanSet() && structField.Tag == "inject" {
            // 获取当前反射值对象的反射类型对象
            ft := f.Type()
            // 从values中获取以ft为键的值
            v := inj.Get(ft)
            if !v.IsValid() {
                return fmt.Errorf("Value not found for type %v", ft)
            }
            f.Set(v)
        }
    }
    return nil
}

Apply 方法是用于对 struct 的字段进行注入,参数为指向底层类型为结构体的指针。

可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),并且此字段的 tag 设置为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)
}

运行结果:

s.Name = 张三
s.Gender = 男
s.Age = 26

拓展:inject 官方文档

– import “github.com/codegangsta/inject”

inject包提供了多种对实体的映射和依赖注入方式。

用法

func InterfaceOf

就是取类型,不过传入的参数要是接口类型的指针

例如

type SpecialString interface{}
func main(){
	fmt.Println(inject.InterfaceOf((*SpecialString)(nil))) // main.SpecialString
}
func InterfaceOf(value interface{}) reflect.Type

函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针,将抛出一个panic异常。

type Applicator
type Applicator interface {
    // 在Type map中维持对结构体中每个域的引用并用'inject'来标记
    // 如果注入失败将会返回一个error.
    Apply(interface{}) error
}

Applicator接口表示到结构体的依赖映射关系。

type Injector
type Injector interface {
    Applicator
    Invoker
    TypeMapper
    // SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖,
    // 将会继续从它的父injector中找,直到返回error.
    SetParent(Injector)
}

Injector接口表示对结构体、函数参数的映射和依赖注入。

func New
func New() Injector

New创建并返回一个Injector.

type Invoker
type Invoker interface {
    // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
    // 它将返回reflect.Value的切片,其中存放原函数的返回值。
    // 如果注入失败则返回error.
    Invoke(interface{}) ([]reflect.Value, error)
}

Invoker接口表示通过反射进行函数调用。

type TypeMapper
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
}

TypeMapper接口用来表示基于类型到接口值的映射。

  1. map()

    传入一个参数,该参数的反射类型对象作为values的键,反射值对象作为values的值

  2. mapTo()

    传入两个参数,第一个参数的反射值对象作为values的值,第二个参数传入interfaceOf()获取其类型作为values的键

;