Go语言核心编程第6章 “反射”

学习目标:

Go语言核心编程第6章 “反射”

学习内容:

Go语言核心编程第6章 “反射”

第6章 反射

在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改本身状态或行为的一种能力。通俗地讲,反射就是程序能够在运行时动态地查看自己的状态,并且允许修改自身的行为。
在裸机和汇编语言时代,反射是天然的,只需要修改相关的指令就能查看或修改程序的行为,随着操作系统和高级语言的引入,程序获得操作系统和运行时保护的同时牺牲了灵活性。现代很多高级语言加入对反射的支持,以弥补程序的动态性的不足。
Go 语言在运行时和库的层面提供反射支持。
Go 语言提供了反射功能,支持程序动态地访问变量的类型和值。
Go 语言反射的基础是编译器和运行时把类型信息以合适的数据结构保存在可执行程序中。
Go 提供的 reflect 标准库只是为语言使用者提供一套访问接口,反射实现是语言设计者在设计语言时考虑的。

6.1 基本概念

Go反射基础接口和类型系统。 Go 反射巧妙地借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构 eface , **反射基于这个转换后的数据结构来访问和操作实例的值和类型。**我们知道实例传递给 interface{} 类型, 编译器会进行一个内部的转换,自动创建相关类型数据结构。基于接口这个动态类型转换机制实现反射,事半功倍。

6.1.1 基本数据结构和入口函数

reflect.Type
反射包里面有一个通用的描述类型公共信息的结构 rtype ,示例如下:

//GOROOT src/reflect/type.go
type rtype struct {
	size       uintptr
	ptrdata    uintptr // number of bytes in the type that can contain pointers
	hash       uint32  // hash of type; avoids computation in hash tables
	tflag      tflag   // extra type information flags
	align      uint8   // alignment of variable with this type
	fieldAlign uint8   // alignment of struct field with this type
	kind       uint8   // enumeration for C
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal     func(unsafe.Pointer, unsafe.Pointer) bool
	gcdata    *byte   // garbage collection data
	str       nameOff // string form
	ptrToThis typeOff // type for pointer to this type, may be zero
}
// 数组类型
// arrayType represents a fixed array type.
type arrayType struct {
	rtype
	elem  *rtype // array element type
	slice *rtype // slice type
	len   uintptr
}
// 指针类型
// ptrType represents a pointer type.
type ptrType struct {
	rtype
	elem *rtype // pointer element (pointed at) type
}

rtype 实现了接口 reflect.Type ,Go refelct 通过函数 refelct.TypeOf() 返回一个 Type 类型的接口,使用者通过接口来获取对象的类型信息。
为什么反射接口返回的是一个 Type 接口类型,而不是直接返回 ype 原因很简单,一是因为类型信息是一个只读的信息,不可能动态地修改类型的相关信息,那太不安全了;二是因为不同的类型,类型定义也不一样,使用接口这 一抽象数据结构能够进行统一的抽象。 所以 refelct 包通过 reflect. TypeOf() 函数返回一个 Type 接口变量,通过接口抽象出来的方法访问具体类型的信息。
refelct.TypeOf()的函数原型如下:

形参是一个空接口,返回值是一个Type接口类型
func TypeOf(i interface{}) Type 

( 1 )所有类型通用的方法。示例如下:

// 通过索引值访问方法
Method(int) Method

// 通过方法名获取method
MethodByName(string) (Method, bool)

// 返回一个类型的方法的个数
NumMethod() int

// 返回包含包名的类型方法,对于未命名类型返回的是空
Name() string

// 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
PkgPath() string

// 返回存放该类型的实例需要多大的字节空间
Size() uintptr

// 判断当前类型是否能强制转换为 u 类型变量
ConvertibleTo(u Type) bool

// 判断当前类型是否支持比较
Comparable() bool

// 判断当前类型是否能赋值给type为u的类型变量
AssignableTo(u Type) bool

// Kind返回该函数的底层基础类型
Kind() Kind

// 确定当前类型是否实现了u接口类型
Implements(u Type) bool

( 2 )不同基础类型的专有方法。
这些方法是某种类型特有的,如果不是某种特定的类型却调用了该类型的方法,则会引发 panic 。所 以为了避免 panic 在调用特定类型的专有方法前 ,要清楚地知道该类型是什么 ,如果不确定类型,则要先调用 Kind() 方法确定类型后再调用类型的专有方法。
示例如下:

//	Int*, Uint*, Float*, Complex*: Bits
//	Array: Elem, Len
//	Chan: ChanDir, Elem
//	Func: In, NumIn, Out, NumOut, IsVariadic.
//	Map: Key, Elem
//	Ptr: Elem
//	Slice: Elem
//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

// 返回类型的元素类型
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type

// 返回数值型类型内存占用的位数
// sized or unsized Int, Uint, Float, or Complex kinds.
Bits() int

// struct类型专用方法	
// 返回字段数目
NumField() int

// 通过整数索引获得struct字段
Field(i int) StructField

// 根据嵌入字段获取struct结构体
FieldByIndex(index []int) StructField

// 通过名字查找struct字段
FieldByName(name string) (StructField, bool)

// map类型专用方法,返回map的key类型
Key() Type

// func类型专用方法
// 输入参数个数
NumIn() int

// 返回值的个数
NumOut() int

// 返回第i个返回值类型
Out(i int) Type

// 返回第i个输入参数的类型
In(i int) Type

下面通过一个具体实例来看下 reflect.TypeOf() 函数的基本功能。

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string "学生名字"
	Age  int    `a:"1111"b:"3333"`
}

func main() {
	s := Student{}
	rt := reflect.TypeOf(s)
	fieldName, ok := rt.FieldByName("Name")
	if ok {
		fmt.Println(fieldName.Type)
	}
	/* 可以像 JSON 一样,取 tag 里的数据,多个 tag 之间无逗号, tag不需妥引号*/
	fieldAge, ok2 := rt.FieldByName("Age")
	if ok2 {
		fmt.Println(fieldAge.Tag.Get("a"))
		fmt.Println(fieldAge.Tag.Get("b"))
	}
	fmt.Println("type_Name:", rt.Name())
	fmt.Println("type_NameField:", rt.NumField())
	fmt.Println("type_PkgPath :", rt.PkgPath())
	fmt.Println("type_String :", rt.String())
	fmt.Println("type.Kind.String :", rt.Kind().String())
	fmt.Println("type.String()=", rt.String())
	//获取结构类型的字段名称
	for i := 0; i < rt.NumField(); i++ {
		fmt.Printf("type.Field[%d].Name:=%v \n", i, rt.Field(i).Name)
	}
	sc := make([]int, 10)
	sc = append(sc, 1, 2, 3)
	sct := reflect.TypeOf(sc)
	fmt.Println("sct type.Kind()=", sct.Kind())
	//获取 slce 元素的 Type
	scet := sct.Elem()
	fmt.Println("slice element type.Kind()=", scet.Kind())
	//在Kind中的位置
	fmt.Printf("slice element type.Kind()=%d\n", scet.Kind())
	fmt.Println("slice element type.String()=", scet.String())
	fmt.Println("slice element type.Name()=", scet.Name())
	fmt.Println("slice type.NumMethod()=", scet.NumMethod())
	fmt.Println("slice type.PkgPath()=", scet.PkgPath())
	fmt.Println("slice type.PkgPath()=", sct.PkgPath())
}
1111
3333
type_Name: Student
type_NameField: 2
type_PkgPath : main
type_String : main.Student
type.Kind.String : struct
type.String()= main.Student
type.Field[0].Name:=Name
type.Field[1].Name:=Age
sct type.Kind()= slice
slice element type.Kind()= int
slice element type.Kind()=2
slice element type.String()= int
slice element type.Name()= int
slice type.NumMethod()= 0
slice type.PkgPath()=
slice type.PkgPath()=

对于 reflect.TypeOf(a),传进去的实参 a 有两种类型, 一种是接口变量,另一种是具体类型变量。
如果 a 是具体类型变量 ,则 reflect.TypeOf() 返回的是具体的类型信息;如果 a 是一个接口变量, 则函数的返回结果又分两种情况:如果 a 绑定了具体类型实例, 则返回的是接口的动态类型 ,也就是绑定的具体实例类型的信息,如果 a 没有绑定具体类型实例,则返回的是接口自身的静态类型信息。下面以一个实例来看下这种特性。

package main

import "reflect"

type INT int
type A struct {
	a int
}
type B struct {
	b string
}
type Ita interface {
	String() string
}

func (b B) String() string {
	return b.b
}

func main() {
	var a INT = 12
	var b int = 14

	//实参是具体类型,reflect.TypeOf返回是其静态类型
	ta := reflect.TypeOf(a)
	tb := reflect.TypeOf(b)

	//INT 和 int 是两个类型,两者不相等
	if ta == tb {
		println("ta==tb")
	} else {
		println("ta!=tb")
	}
	
	println(ta.Name())//INT
	println(tb.Name())//int
	//底层数据结构
	println(ta.Kind().String())//int
	println(tb.Kind().String())//int
	
	s1 := A{1}
	s2 := B{"tata"}
	//实参是具体类型,返回其静态类型
	println(reflect.TypeOf(s1).Name()) //A
	println(reflect.TypeOf(s2).Name()) //B
	//Type的Kind()方法返回的是基础类型,类型A和B的底层数据结构都是struct
	println(reflect.TypeOf(s1).Kind().String()) //struct
	println(reflect.TypeOf(s2).Kind().String()) //struct
	
	ita := new(Ita)
	var itb Ita = s2
	//实参是未绑定具体类型的接口,reflect.TypeOf返回的是接口类型本身,也就是接口的静态类型
	println(reflect.TypeOf(ita).Elem().Name()) //Ita
	println(reflect.TypeOf(ita).Elem().Kind().String()) //interface
	//实参是绑定了具体类型的接口类型,reflect.TypeOf返回的是绑定的具体类型,也就是接口的动态类型
	println(reflect.TypeOf(itb).Elem().Name()) //B
	println(reflect.TypeOf(itb).Elem().Kind().String()) //struct
}

reflect.Value
reflect.Value 表示实例的值信息, reflect.Value 是一个 struct ,并提供了一系列的 method 给使用者。先来看一 Value 的基本数据结构:

type Value struct {
	//typ holds the type of the value represented by a Value .
	typ *rtype

	//Pointer valued data or, if flagindir is set, pointer to data .
	//Valid when either flagindir is set or typ . pointers( ) is true .
	ptr unsafe.Pointer
	//
	flag
}

reflect.Value 总共有三个字段,一个是值的类型指针 typ,另一个是执行值的指针 ptr,最后一个是标记字段 flag。
反射包通过 reflect.ValueOf() 函数获取实例信息。reflect.ValueOf() 的原型如下:

func ValueOf(i interface{}) Value

输入参数是空接口,输出是一个 Value 类型的变量。Value 本身提供了丰富的 API 给用户使用。以下是一个简单示例。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func (this User) String() {
	println("User:", this.Id, this.Name, this.Age)
}

func Info(o interface{}) {
	v := reflect.ValueOf(o)
	t := v.Type()
	//类型名称
	println("Type:", t.Name())

	//访问接口字段名、字段类型名和字段值信息
	println("Fields:")
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i).Interface()
		switch value.(type) {
		case int:
			fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
		case string:
			fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
		default:
			fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
		}
	}
}
func main() {
	u := User{1, "Tom", 30}
	Info(u)
}
Type: User
Fields:
Id: int = 1
Name: string = %!d(string=Tom)
Age: int = 30
6.1.2 基础类型

Type 接口有一个Kind() 方法,返回的是一个整数枚举值,不同的值代表不同的类型。
Go 总共定义了26种基础类型,具体如下:

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

底层类型和基础类型
底层类型和基础类型的区别在于,基础类型是基于抽象的类型划分,底层类型是针对每一个具体的类型来定义。实例如下:

// 声明一个结构体类型
type MyStruct struct {
	Value int
}
fmt.Println(reflect.TypeOf(MyStruct{}).Kind()) //struct

// 将结构体类型声明为 int 的类型别名
type MyIntAlias = int

MyStruct 和 MyIntAlias 底层类型是 int,基础类型分别为 struct 和 int

6.1.3 类型汇总

简单类型 复合类型 类型字面量 自定义类型
命名类型 未命名类型
接口类型 具体类型
底层类型 基础类型
动态类型 静态类型
Go语言核心编程第6章 “反射”_第1张图片

6.2 反射规则

Go语言核心编程第6章 “反射”_第2张图片

6.2.1 反射 API

反射 API 的分类总结如下。
1. 从实例到 Value
通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如

func ValueOf(i interface{}) Value

2. 从实例到 Type
通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:

func TypeOf(i interface{}) Type

3. 从 Type 到 Value
Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但是可以通过该 Type 构建一个新的实例的 Value。reflect 包提供了两种方法,示例如下:

//New 返回的是一个Value,该 Value 的 type 为 PtrTo (typ), 即 Value 的 Type 是指定 typ 的指针类型
func New(typ Type) Value
//Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址,值不可改变
func Zero(typ Type) Value

如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的。例如:

func NewAt(typ Type, p unsafe.Pointer) Value

4.从 Value 到 Type
从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:

func (v Value) Type() Type

5.从 Value 到实例
Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转化。例如:

//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
//可以使用接口类型查询去还原为具体的类型
func (v Value) Interface() (i interface {}) 
//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
func (v Value) Bool () bool 
func (v Value) Float() float64 
func (v Value) Int() int64 
func (v Value) Uint() uint64

6. 从 Value 的指针到值
从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。

如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如果 v 类型是指针,则返回指针值的 Value,否则引起 panic
func (v Value) Elem() Value
如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value

7. Type指针和值的相互转化
( 1 ) 指针类型 Type 到值类型 Type。例如:

//t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic
//Elem 返回的是其内部元素的Type 
t.Elem() Type

( 2 ) 值类型 Type 到指针类型 Type。例如:

//PrtTo 返回的是指向 t 的指针型 Type
func PtrTo(t Type) Type

8. Value 值的可修改性
Value 值的修改涉及如下两个方法:

//通过 CanSet 判断是否能修改
func (v Value) CanSet() bool
//通过 Set 进行修改
func (v Value) Set(x Value)

Value 值在什么情况下可以修改?
实例对象传递给接口的是一个完全的值拷贝,如果调用反射方法 reflect.ValueOf() 传入的是一个值类型变量,则获得的 Value 实际上是原对象的一个副本,无法被修改。如果传进入的是一个指针,虽然接口内部转化的也是指针的副本,但通过指针还是可以访问到最原始的对象,所以此种情况获得的 Value 是可以修改的。下面看一个简单的示例:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func main() {
	u := User{1, "Tom", 30}

	va := reflect.ValueOf(u)
	vb := reflect.ValueOf(&u)

	//值类型是可修改的
	fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet()) //false false

	//指针类型是可修改的
	fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet()) //false,true

	fmt.Printf("%v\n", vb)
	name := "shine"
	vc := reflect.ValueOf(name)
	vb.Elem().FieldByName("Name").Set(vc)
	fmt.Printf("%v\n", vb)
}
false false
false true
&{1 Tom 30}
&{1 shine 30}
6.2.2 反射三定律

( 1 ) 反射可以从接口值得到发射对象。
( 2 ) 反射可以从反射对象获得接口值。
( 3 ) 若要修改一个反射对象,其值必须可以修改。

6.3 inject 库

6.3.1 inject 库是什么

inject 借助反射提供了对 2 种类型实体的注入 : 函数和结构

6.3.2 依赖注入和控制反转

在介绍 inject 之前先简单介绍"依赖注入"和"控制反转"的概念。
正常情况下,对函数或方法的调用是调用方的主动直接行为,调用方清楚地知道被调的函数名是什么,参数有哪些 类型,直接主动地调用;包括对象的初始化也是显式地直接初始化。所谓的“控制反转”就是将这种主动行为变成间接的行为,主调方不是直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为我们称为"控制反转",控制反转可以解耦调用方和被调方。
"库"和"框架"能很好地解释“控制反转”的概念。一般情况下,使用库的程序是程序主动地调用库的功能,但使用框架的程序常常由框架驱动整个程序,在框架下写的业务代码是被框架驱动的,这种模式就是“控制反转” 。
"依赖注入"是实现"控制反转"的一种方法,如果说"控制反转"是一种设计思想,那么"依赖注入"就是这种思想的一种实现,通过注入的参数或实例的方式实现控制反转。如果没有特殊说明,我们通常说的"依赖注入"和"控制反转"是一个东西。
读者可能会疑惑,为什么不直接光明正大地调用,而非要拐弯抹角地进行间接调用,控制反转的价值在哪里呢? 一句话"解耦",有了控制反转就不需要调用者将代码写死,可以让控制反转的框架代码读取配置,动态地构建对象,这一点在 Java pring 框架中体现得尤为突出。
控制反转是解决复杂问题一种方法,特别是在 Web 框架中为路由和中间件的灵活注入提供了很好的方法。 但是软件开发没有灵丹, 当问题足够复杂时,应该考虑的是服务拆分,而不是复杂的逻辑用 个"大盒子 "装起来, 看起来干净了 ,但也只是看起来干净,实现还是很复杂,这也是使用框架带来的副作用。

6.3.3 injetc 实践

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"]()
}
f1
f2

但是这有个缺陷,就是 map Value 类型被写成 fun(),不同参数和返回值的类型的函数并不能通用。将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)
}
name=tom, company=tencent, level=T4, age=23!

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

package main

import (
	"fmt"
	"github.com/codegangsta/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)//s={tom tencent T4 23}
}

type S1 interface {
}

type S2 interface {
}

type Staff struct {
	Name    string `inject`
	Company S1     `inject`
	Level   S2     `inject`
	Age     int    `inject`
}
s={tom tencent T4 23}
6.3.3 injetc 原理分析

inject 包只有 178 行代码,却提供了一个完美的依赖注入实现,下面采用自顶向下的方法分析其实现原理。
入口函数New()
inject.New() 函数构建一个具体类型 injector 实例作为内部注入引擎,返回的是一个Injector类型的接口。这里也体现了一种面向接口的设计思想:对外暴露接口方法,对外隐藏内部实现。
示例如下:

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

接口设计
下面来看一下具体的接口设计,Injector 暴露了所有方法给外部使用者,这些方法又可以归纳为两大类。第一类方法是对参数注入进行初始化,将结构类型的字段的注入和函数的参数注入统一成一套方法实现;第二类是专用注入实现,分别是生成结构对象和调用函数方法。
在代码设计上,inject 将接口的粒度拆分得很细,将多个接口组合为一个大的接口,这也符合 Go 的 Duck 类型接口设计的原则。Injector 按照上述方法拆分为三个接口。示例如下:

type Injector interface {
	//抽象生成注入结构实例的接口
	Applicator

	//抽象函数调用的接口
	Invoker

	//抽象注入参数的接口
	TypeMapper
	
	//实现一个注入实例链,下游的能覆盖上游的类型
	SetParent(Injector)
}

TypeMapper 接口实现对注入参数操作的汇总,包括设置和查找相关的类型和值的方法。注意:无论函数的实参,还是结构的字段,在 inject 内部,都存放 map[reflect.Type]reflect.Value 类型的 map 里面。

type TypeMapper interface {
	//如下三个方法是设置参数
	Map(interface{}) TypeMapper
	MapTo(interface{}, interface{}) TypeMapper
	Set(reflect.Type, reflect.Value) TypeMapper

	//查找参数
	Get(reflect.Type) reflect.Value
}

Invoker 接口中 Invoke 方法是对被注入实参函数的调用:

type Invoker interface {
	Invoke(interface(})([]reflect.Value,error)
}

Applicator 接口中 Apply 方法实现对结构的注入:

type Applicator interface {
	Apply(interface())error
}

梳理整个 inject 包的处理流程:
( 1 ) 通过 inject.New() 创建注入引擎,注入引擎被隐藏,返回的是 Injector 接口类型变量。
( 2 ) 调用 TypeMapper 接口( Injector 内嵌 TypeMapper )的方法注入 struct 的字段值或函数的实参值。
( 3 ) 调用 Invoker 方法执行被注入的函数,或者调用 Applicator 接口方法获得被注入后的结构实例。
内部实现
下面具体看一下 inject 内部注入引擎 injector 的实现,首先看一下 injector 的数据结构。

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

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

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

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

func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
	//获取函数类型的Type
	t := reflect.TypeOf(f)

	//构造一个存放函数实参Value值的数组
	var in = make([]reflect.Value, t.NumIn()) 

	//使用反射获取函数实参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
}

inject 对函数注入调用实现很简洁,就是从 injector 里面获取函数实参,然后调用函数。通过对 inject 包的分析,认识到其“短小精悍”、功能强大,这些实现的基础是依靠反射。
但同时注意到包含反射的代码相对来说复杂难懂,虽然 inject 的实现只有短短 200 行代码,但阅读起来并不是很流畅。所以说反射是一把双刃剑,好用但代码不好读。

6.4 反射的优缺点

6.4.1 反射的优点

1. 通用性
特别是一些类库和框架代码需要一种通用的处理模式,而不是针对每一种场景做硬编码处理,此时借助反射可以极大地简化设计
2. 灵活性
反射提供了一种程序了解自己和改变自己的能力,这为一些测试工具的开发提供了有力的支持。

6.4.2 反射的缺点

1. 反射是脆弱的
由于反射可以在程序运行时修改程序的状态,这种修改没有经过编译器的严格检查,不正确的修改很容导致程序的崩溃。
2.反射是晦涩难懂得
语言的反射接口由于涉及语言的运行时,没有具体的类型系统的约束,接口的抽象级别高但实现细节复杂,导致使用反射的代码难以理解。
3. 有部分性能损失
反射提供动态修改程序状态的能力,必然不是直接的地址引用,而是要借助运行时构造一个抽象层,这种间接访问会有性能的损失。

6.4.3 反射的最佳实践

( 1 )在库或框架内部使用反射,而不是把反射接口暴露给调用者,复杂性留在内部,简单放到接口。
( 2 )框架代码才考虑使用反射,一般的业务代码没有必要抽象到反射的层次,这种过度设带来复杂度的提升,使得代码难以维护
( 3 )除非没有其他办法,否则不要使用反射技术。

你可能感兴趣的:(golang,开发语言,后端)