Go的反射有哪些应用?
IDE中代码的自动补全对象序列化fmt函数的相关实现ORM框架什么情况下需要使用反射?
不能明确函数调用哪个接口,需要根据传入的参数在运行时决定。不能明确传入函数的参数类型,需要在运行时处理任意对象。反射对性能有消耗,而且可读性低,能不用就不要用反射。
如何比较两个对象完全相同?
Go中提供了一个函数可以实现这个功能:
func DeepEqual(x, y interface{}) bool
DeepEqual
函数的参数是两个interface
,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。
先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。
例如这个代码:
type MyInt inttype YourInt intfunc main() {m := MyInt(1)y := YourInt(1)fmt.Println(reflect.DeepEqual(m, y))}
这个代码的结果是false。
上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是MyInt
,后者是YourInt
,因此两者不是“深度”相等。
来看一下源码:
func DeepEqual(x, y any) bool {if x == nil || y == nil {return x == y}v1 := ValueOf(x)v2 := ValueOf(y)if v1.Type() != v2.Type() {return false}return deepValueEqual(v1, v2, make(map[visit]bool))}
首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true
接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。
最后,最核心的内容在子函数deepValueEqual
中。
然后我们来看一下deepValueEqual
的源码:
// Tests for deep equality using reflected types. The map argument tracks// comparisons that have already been seen, which allows short circuiting on// recursive types.func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {if !v1.IsValid() || !v2.IsValid() {return v1.IsValid() == v2.IsValid()}if v1.Type() != v2.Type() {return false}// We want to avoid putting more in the visited map than we need to.// For any possible reference cycle that might be encountered,// hard(v1, v2) needs to return true for at least one of the types in the cycle,// and it's safe and valid to get Value's internal pointer.hard := func(v1, v2 Value) bool {switch v1.Kind() {case Pointer:if v1.typ.ptrdata == 0 {// go:notinheap pointers can't be cyclic.// At least, all of our current uses of go:notinheap have// that property. The runtime ones aren't cyclic (and we don't use// DeepEqual on them anyway), and the cgo-generated ones are// all empty structs.return false}fallthroughcase Map, Slice, Interface:// Nil pointers cannot be cyclic. Avoid putting them in the visited map.return !v1.IsNil() && !v2.IsNil()}return false}if hard(v1, v2) {// For a Pointer or Map value, we need to check flagIndir,// which we do by calling the pointer method.// For Slice or Interface, flagIndir is always set,// and using v.ptr suffices.ptrval := func(v Value) unsafe.Pointer {switch v.Kind() {case Pointer, Map:return v.pointer()default:return v.ptr}}addr1 := ptrval(v1)addr2 := ptrval(v2)if uintptr(addr1) > uintptr(addr2) {// Canonicalize order to reduce number of entries in visited.// Assumes non-moving garbage collector.addr1, addr2 = addr2, addr1}// Short circuit if references are already seen.typ := v1.Type()v := visit{addr1, addr2, typ}if visited[v] {return true}// Remember for later.visited[v] = true}switch v1.Kind() {case Array:for i := 0; i < v1.Len(); i++ {if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {return false}}return truecase Slice:if v1.IsNil() != v2.IsNil() {return false}if v1.Len() != v2.Len() {return false}if v1.UnsafePointer() == v2.UnsafePointer() {return true}// Special case for []byte, which is common.if v1.Type().Elem().Kind() == Uint8 {return bytealg.Equal(v1.Bytes(), v2.Bytes())}for i := 0; i < v1.Len(); i++ {if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {return false}}return truecase Interface:if v1.IsNil() || v2.IsNil() {return v1.IsNil() == v2.IsNil()}return deepValueEqual(v1.Elem(), v2.Elem(), visited)case Pointer:if v1.UnsafePointer() == v2.UnsafePointer() {return true}return deepValueEqual(v1.Elem(), v2.Elem(), visited)case Struct:for i, n := 0, v1.NumField(); i < n; i++ {if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {return false}}return truecase Map:if v1.IsNil() != v2.IsNil() {return false}if v1.Len() != v2.Len() {return false}if v1.UnsafePointer() == v2.UnsafePointer() {return true}for _, k := range v1.MapKeys() {val1 := v1.MapIndex(k)val2 := v2.MapIndex(k)if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {return false}}return truecase Func:if v1.IsNil() && v2.IsNil() {return true}// Can't do better than this:return falsecase Int, Int8, Int16, Int32, Int64:return v1.Int() == v2.Int()case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:return v1.Uint() == v2.Uint()case String:return v1.String() == v2.String()case Bool:return v1.Bool() == v2.Bool()case Float32, Float64:return v1.Float() == v2.Float()case Complex64, Complex128:return plex() == plex()default:// Normal equality sufficesreturn valueInterface(v1, false) == valueInterface(v2, false)}}
这个代码的思路很清晰,就是分别递归调用deepValueEqual
函数,一直递归到最进本的数据类型,比较int
,string
等可以直接得出true
或者false
,再一层层的返回,最终得到深度相等的比较结果。
Go语言是如何实现反射的?
interface
,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。
Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。
type和interface
我们需要先介绍一下什么叫做静态类型,什么叫做动态类型。
静态类型
所谓的静态类型(即 static type),就是变量声明的时候的类型。
var age int // int 是静态类型var name string // string 也是静态类型
它是你在编码时,肉眼可见的类型。
动态类型
所谓的动态类型(即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。
这是什么意思呢?
我们都知道空接口可以承接什么问题类型的值,什么 int 呀,string 呀,都可以接收。
比如下面这几行代码
var i interface{} i = 18 i = "Go编程时光"
第一行:我们在给i
声明了interface{}
类型,所以i
的静态类型就是interface{}
第二行:当我们给变量i
赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。
第三行:当我们给变量i
赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。
从以上,可以知道,不管是i=18
,还是i="Go编程时光"
,都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的 动态类型是程序运行时系统才能看见的类型。
Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如int, float64, []int
等等。注意,这个类型是声明时候的类型,不是底层数据类型。
Go官方的博客里面就举过一个例子:
type MyInt int var i int var j MyInt
尽管 i,j 的底层类型都是 int,但我们知道,他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是MyInt
。
反射跟interface{}
的关系十分密切,因此我们需要先学习一下interface{}
的原理。
interface{}
非空interface{}
type iface struct {tab *itabdata unsafe.Pointer}type itab struct {inter *interfacetype_type *_typehash uint32 // copy of _type.hash. Used for type switches._[4]bytefun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.}type interfacetype struct {typ_typepkgpath namemhdr []imethod}type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32tflagtflagalignuint8fieldAlign uint8kind uint8// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal func(unsafe.Pointer, unsafe.Pointer) bool// gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata *bytestr nameOffptrToThis typeOff}
itab
主要由具体类型_type
和结构类型interfacetype
组成。
我们可以用一张图来理顺中间的关系:
空interface{}
type eface struct {_type *_typedata unsafe.Pointer}
相比iface
,eface
就比较简单了。只维护了一个_type
字段,表示空接口所承载的具体的实体类型。data
描述了具体的值。
接口变量可以存储任何实现了接口定义的所有方法的变量。
Go中常见的接口Reader
和Writer
接口:
type Reader interface {Read(p []byte) (n int, err error)}type Writer interface {Write(p []byte) (n int, err error)}
var r io.Readertty, err := os.OpenFile("./", os.O_RDWR, 0)if err != nil {return nil, err}r = tty
首先声明r
的类型是io.Reader
,注意,这是r
的静态类型,此时它的动态类型为nil
,并且它的动态值也是nil
。
之后,r = tty
这一语句,将r
的动态类型变成*os.File
,动态值则变成非空,表示打开的文件对象。这时,r 可以用<value, type>
对来表示为:<tty, *os.File>
。
注意看上图,此时虽然fun
所指向的函数只有一个Read
函数,其实*os.File
还包含Write
函数,也就是说*os.File
其实还实现了io.Writer
接口。因此下面的断言语句可以执行:
var w io.Writerw = r.(io.Writer)
之所以用断言,而不能直接赋值,是因为r
的静态类型是io.Reader
,并没有实现io.Writer
接口。断言能否成功,看r
的动态类型是否符合要求。
这样,w 也可以表示成<tty, *os.File>
,仅管它和r
一样,但是 w 可调用的函数取决于它的静态类型io.Writer
,也就是说它只能有这样的调用形式:w.Write()
。w
的内存形式如下图:
最后,我们再来一个赋值:
var empty interface{}empty = w
由于empty
是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。
从上面的三张图可以看到,interface
包含三部分的信息:_type
是类型信息,*data
指向实际类型的实际值,itab
包含实际类型的信息,包括大小,包路径,还包含绑定在类型上的各种方法。
反射的基本函数
reflect
包里面定义了一个接口和一个结构体,reflect.Type
是一个接口,reflect.Value
是一个结构体,它们提供很多函数来存储在接口里面的类型信息。
reflect.Type
主要提供关于类型相关的信息,所以它和_type
关联比较紧密;reflect.Value
则结合_type
和data
两者,因此程序员可以获取甚至改变类型的值。
reflect
包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:
func TypeOf(i interface{}) Type func ValueOf(i interface{}) Value
TypeOf
函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的interface{}
,调用这个函数的时候,实参会先被转化为interface{}
类型。这样,实参的类型信息,方法集,值信息都存储到interface{}
变量里了。
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)}
这里的emptyInterface
和上面提到的eface
是一回事(字段名略有差异,字段是相同的),并且在不同的源码包:前者在reflect
包,后者在runtime
包。eface.typ
就是动态类型。
type emptyInterface struct {typ *rtypeword unsafe.Pointer}
然后toType
函数只是做了一个类型转换而已:
func toType(t *rtype) Type {if t == nil {return nil}return t}
注意看,返回值Type
实际上是一个接口,定义了很多方法,用来获取类型的相关的各种信息,而*rtype
实现了Type
接口。
type Type interface {// Methods applicable to all types.// 此类型的变量对齐后占用的字节数Align() int// 如果是struct自动,对齐后占用的字节数FieldAlign() int// 返回类型方法集李的第`i`(传入的参数)个方法Method(int) Method// 通过名称获取方法MethodByName(string) (Method, bool)// 获取类型方法集里导出的方法个数NumMethod() int// 类型名称Name() string// 返回类型所在的路径,如: encoding/base64PkgPath() string// 返回类型的大小,和unsafe.Sizeof功能类似Size() uintptr// 返回类型的字符串表示形式String() string// 返回类型的类型值Kind() Kind// 类型是否实现了接口 uImplements(u Type) bool// 是否可以赋值给 uAssignableTo(u Type) bool// 是否可以类型转换成 uConvertibleTo(u Type) bool// 类型是否可以比较Comparable() bool// 类型占据的位数Bits() int// 返回通道的方向,只能是chan类型调用ChanDir() ChanDir// 返回类型是否是可变参数,只能是func类型调用IsVariadic() bool// 返回内部子元素类型, 只能由类型Array, Chan, Map, Ptr, or Slice调用Elem() Type// 返回结构体类型的第i个字段,只能是结构体类型调用// 如果i超过了字段数,就会panicField(i int) StructField// 返回嵌套的结构体的字段FieldByIndex(index []int) StructField// 通过字段名获取字段FieldByName(name string) (StructField, bool)// 返回名称符合func函数的字段FieldByNameFunc(match func(string) bool) (StructField, bool)// 获取函数类型的第i个参数的类型In(i int) Type// 返回map的key类型,只能由类型map调用Key() Type// 返回Array的长度,只能由Array调用Len() int// 返回类型字段的数量,只能由类型Struct调用NumField() int// 返回函数类型的输入参数个数NumIn() int// 返回函数类型的返回值个数NumOut() int// 返回函数类型的第i个值的类型Out(i int) Type// 返回类型结构体的相同部分common() *rtype// 返回类型结构体的不同部分uncommon() *uncommonType}
可见Type
定义了非常多的方法,通过它们可以获取到类型的所有信息。
注意到Type
方法集的倒数第二个方法common
返回的rtype
类型,它和_type
是一回事,而且源代码里面也注释了,两边要保持同步。
type rtype struct {size uintptrptrdata uintptrhash uint32tflagtflagalignuint8fieldAlign uint8kind uint8alg *typeAlggcdata*bytestr nameOffptrToThis typeOff}
所有的类型都会包含rtype
这个字段,表示各种类型的公共信息;另外,不同类型包含自己的一些独特的部分。
比如下面的arrayType
和chanType
都包含rytpe
,而前者还包含 slice,len 等和数组相关的信息;后者则包含dir
表示通道方向的信息。
// arrayType represents a fixed array type.type arrayType struct {rtype `reflect:"array"`elem *rtype // array element typeslice *rtype // slice typelen uintptr}// chanType represents a channel type.type chanType struct {rtype `reflect:"chan"`elem *rtype // channel element typedir uintptr // channel direction (ChanDir)}
注意到,Type
接口实现了String()
函数,满足fmt.Stringer
接口,因此使用fmt.Println
打印的时候,输出的是String()
的结果。另外,fmt.Printf()
函数,如果使用%T
来作为格式参数,输出的是reflect.TypeOf
的结果,也就是动态类型。例如:
fmt.Printf("%T", 3) // int
TypeOf
函数讲完了,我们接下来来看一下ValueOf
函数。返回值reflect.Value
表示interface{}
里面存储的实际变量,它能提供实际变量的各种信息。
源码如下:
func ValueOf(i interface{}) Value {if i == nil {return Value{}}// ……return unpackEface(i)}// 分解 efacefunc unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, e.word, f}}
从源码看,比较简单:将先将i
转换成*emptyInterface
类型, 再将它的typ
字段和word
字段以及一个标志位字段组装成一个Value
结构体,而这就是ValueOf
函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。
Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:
// 设置切片的 len 字段,如果类型不是切片,就会panicfunc (v Value) SetLen(n int)// 设置切片的 cap 字段func (v Value) SetCap(n int)// 设置字典的 kvfunc (v Value) SetMapIndex(key, val Value)// 返回切片、字符串、数组的索引 i 处的值func (v Value) Index(i int) Value// 根据名称获取结构体的内部字段值func (v Value) FieldByName(name string) Value// ……// 用来获取 int 类型的值func (v Value) Int() int64// 用来获取结构体字段(成员)数量func (v Value) NumField() int// 尝试向通道发送数据(不会阻塞)func (v Value) TrySend(x reflect.Value) bool// 通过参数列表 in 调用 v 值所代表的函数(或方法func (v Value) Call(in []Value) (r []Value) // 调用变参长度可变的函数func (v Value) CallSlice(in []Value) []Value
另外,通过Type()
方法和Interface()
方法可以打通interface
、Type
、Value
三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface。
总结一下:TypeOf()
函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息;ValueOf()
函数返回一个结构体变量,包含类型信息以及实际值。
上图中,rtye
实现了Type
接口,是所有类型的公共部分。emptyface 结构体和 eface 其实是一个东西,而 rtype 其实和 _type 是一个东西,只是一些字段稍微有点差别,比如 emptyface 的 word 字段和 eface 的 data 字段名称不同,但是数据型是一样的。
反射的三大的定律
根据 Go 官方关于反射的博客,反射有三大定律:
Reflection goes from interface value to reflection object.Reflection goes from reflection object to interface value.To modify a reflection object, the value must be settable.
第一条是最基本的:反射是一种检测存储在interface
中的类型和值机制。这可以通过TypeOf
函数和ValueOf
函数得到。
第二条实际上和第一条是相反的机制,它将ValueOf
的返回值通过Interface()
函数反向转变成interface
变量。
前两条就是说接口型变量
和反射类型对象
可以相互转化,反射类型对象实际上就是指的前面说的reflect.Type
和reflect.Value
。
第三条不太好懂:如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。
举一个经典例子:
var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) // Error: will panic.
执行上面的代码会产生 panic,原因是反射变量v
不能代表x
本身,为什么?因为调用reflect.ValueOf(x)
这一行代码的时候,传入的参数在函数内部只是一个拷贝,是值传递,所以v
代表的只是x
的一个拷贝,因此对v
进行操作是被禁止的。
可设置是反射变量Value
的一个性质,但不是所有的Value
都是可被设置的。
就像在一般的函数里那样,当我们想改变传入的变量时,使用指针就可以解决了。
var x float64 = 3.4p := reflect.ValueOf(&x)fmt.Println("type of p:", p.Type())fmt.Println("settability of p:", p.CanSet())
输出是这样的:
type of p: *float64settability of p: false
p
还不是代表x
,p.Elem()
才真正代表x
,这样就可以真正操作x
了:
v := p.Elem()v.SetFloat(7.1)fmt.Println(v.Interface()) // 7.1fmt.Println(x) // 7.1
关于第三条,记住一句话:如果想要操作原变量,反射变量Value
必须要 hold 住原变量的地址才行。
TypeOf和ValueOf
TypeOf
返回Type
接口,ValueOf
返回Value
结构体,此二者是后续所有操作的核心
TypeOf
reflect.TypeOf()
返回的是一个Type
接口,定义了大多数对类型元数据的操作,比如String()
是获取类型名,Kind()
返回具体类型的枚举值,NumIn()
为传入的是当函数类型时返回所有传入参数的数量,同理NumOut()
返回的是函数返回值的数量。
有接口就有结构体,我们推测底层一定存在一个结构体。
// Type接口原型: 我们需要终点关注的String() Elem() Kind()type Type interface {// .......// String returns a string representation of the type.// The string representation may use shortened package names// (e.g., base64 instead of "encoding/base64") and is not// guaranteed to be unique among types. To test for type identity,// compare the Types directly.String() string// Kind returns the specific kind of this type.Kind() Kind// Elem returns a type's element type.// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.Elem() Type// In returns the type of a function type's i'th input parameter.// It panics if the type's Kind is not Func.// It panics if i is not in the range [0, NumIn()).In(i int) Type// Key returns a map type's key type.// It panics if the type's Kind is not Map.Key() Type// Len returns an array type's length.// It panics if the type's Kind is not Array.Len() int// NumField returns a struct type's field count.// It panics if the type's Kind is not Struct.NumField() int// NumIn returns a function type's input parameter count.// It panics if the type's Kind is not Func.NumIn() int// NumOut returns a function type's output parameter count.// It panics if the type's Kind is not Func.NumOut() int// Implements reports whether the type implements the interface type u.Implements(u Type) bool// .......}
我们先来看一下TypeOf
的源码:
// TypeOf returns the reflection Type that represents the dynamic type of i.// If i is a nil interface value, TypeOf returns nil.func TypeOf(i any) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)}
先把runtime.eface
类型转换成emptyInterface
。这两个类型本质上都是一样的。然后调用toType
函数将其包装成Type类型的返回值
func toType(t *rtype) Type {if t == nil {return nil}return t}
因此到这里我们可以明确,rtype
肯定实现了Type
接口。
然后我们来看一下rtype
这个方法:
// rtype must be kept in sync with ../runtime/type.go:/^type._type.type rtype struct {size uintptr ptrdata uintptr // number of bytes in the type that can contain pointershash uint32 // hash of type; avoids computation in hash tablestflagtflag // extra type information flagsalignuint8 // alignment of variable with this typefieldAlign uint8 // alignment of struct field with this typekind uint8 // enumeration for C// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equalfunc(unsafe.Pointer, unsafe.Pointer) boolgcdata *byte // garbage collection datastr nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero}
接下来我们再来看看rtype
结构体,可见它就是我们的类型元数据信息,包括类型、大小、对其方式、哈希值等,在type.go的源代码中rtype
实现了Type
接口的方法,我们传入的动态类型都指向了这个类型元数据。
ValueOf
ValueOf
返回一个Value
结构体,我们其实可以将其理解为:对我们传入的变量进行了重新包装,将它的数据指针、类型元数据、标识符一并打包在一个结构体中返回,供我们后续使用:
type Value struct {typ *rtype // 反射变量的类型元数据指针ptr unsafe.Pointer // 数据地址flag // 位标识符:描述信息,是否为指针,是否为方法等,是否只读等}// ValueOf函数原型func ValueOf(i interface{}) Value {if i == nil {return Value{}}// TODO: Maybe allow contents of a Value to live on the stack.// For now we make the contents always escape to the heap. It// makes life easier in a few places (see chanrecv/mapassign// comment below).// 官方再这个函数中已经明确说明将参数i逃逸到堆上escapes(i)return unpackEface(i)}// unpackEface 函数本质上做了三件事情:// 1转换为将runtime.eface类型的i转换为emptyInterface// 2判明e.typ是否为空,如果为空返回一个空Value结构体// 3如果不为空,根据e.typ的类型明确要返回的flag标识符// 最后将三者联合在一起作为Value结构体返回func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))// NOTE: don't read e.word until we know whether it is really a pointer or not.t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, e.word, f}}
从ValueOf
的函数原型中我们可以看到其传入的函数参数是一个interface{}
的空类型。
然而最后会将传入的参数逃逸到堆上,所以我们要注意如下一个问题:
func main() {var s string = "this is sparta"//从编译角度看,这里可以分成两步// 1. 是临时创建一个s的拷贝// 2. s的拷贝作为参数传入ValueOf之和,被显式的逃逸到了堆上// 3. 返回值中svalue指向ptr,是堆上那个s的地址,并非我们原先定义的svar svalue = reflect.ValueOf(&s)svalue.SetString("this is not sparta")}
SetString
的函数原型是:
// SetString sets v's underlying value to x.// It panics if v's Kind is not String or if CanSet() is false.func (v Value) SetString(x string) {// 确认必须可以被赋值v.mustBeAssignable()// 确认必须是String类型v.mustBe(String)// 直接指针操作赋值*(*string)(v.ptr) = x}
然后我们来看一下打印的结果:
panic: reflect: reflect.Value.SetString using unaddressable value
这里我们如果要完成原本操作需要借助Elem()
func main() {var s string = "this is sparta"// 这里主要要传递s的指针// 仍然分为两部分参数拷贝了一个s的地址进入ValueOf// 仍然将其逃逸到了堆上,此时svalue返回的是Value.ptr = 存放s地址的堆上的地址// 比如s的地址为0x01,到了堆上,堆地址为:0xA,存放数据为0x01,Value.ptr = 0xA// 相当于一个二级指针,因此直接SetString是没有用的,直接panic// 需要通过Elem()函数 => ptr = *(*unsafe.Pointer)(ptr)// 返回其指针指向的值,相当于二级指针解引用为一级指针// 如此一来 svalue = svalue.Elem()之和,svalue就是&s// 我们就可以对其SetString修改var svalue = reflect.ValueOf(&s).Elem()svalue.SetString("this is not sparta")fmt.Println(s)}
我们来看一下Elem()
函数的源码:
// Elem returns the value that the interface v contains// or that the pointer v points to.// It panics if v's Kind is not Interface or Pointer.// It returns the zero Value if v is nil.func (v Value) Elem() Value {k := v.kind()switch k {case Interface:var eface anyif v.typ.NumMethod() == 0 {eface = *(*any)(v.ptr)} else {eface = (any)(*(*interface {M()})(v.ptr))}x := unpackEface(eface)if x.flag != 0 {x.flag |= v.flag.ro()}return xcase Pointer:ptr := v.ptrif v.flag&flagIndir != 0 {if ifaceIndir(v.typ) {// This is a pointer to a not-in-heap object. ptr points to a uintptr// in the heap. That uintptr is the address of a not-in-heap object.// In general, pointers to not-in-heap objects can be total junk.// But Elem() is asking to dereference it, so the user has asserted// that at least it is a valid pointer (not just an integer stored in// a pointer slot). So let's check, to make sure that it isn't a pointer// that the runtime will crash on if it sees it during GC or write barriers.// Since it is a not-in-heap pointer, all pointers to the heap are// forbidden! That makes the test pretty easy.// See issue 48399.if !verifyNotInHeapPtr(*(*uintptr)(ptr)) {panic("reflect: reflect.Value.Elem on an invalid notinheap pointer")}}ptr = *(*unsafe.Pointer)(ptr)}// The returned value's address is v's value.if ptr == nil {return Value{}}tt := (*ptrType)(unsafe.Pointer(v.typ))typ := tt.elemfl := v.flag&flagRO | flagIndir | flagAddrfl |= flag(typ.Kind())return Value{typ, ptr, fl}}panic(&ValueError{"reflect.Value.Elem", v.kind()})}
如何验证Elem()的作用?
type actorix struct {idintname stringnumber float64}func main() {var a = actorix{id:3,name: "vs",number: 8.99,}// 注意,这里我们传入的是&avar atype = reflect.ValueOf(&a)fmt.Println("kind of atype is: ", atype.Kind())atype = atype.Elem()fmt.Println("kind of atype.Elem() is: ", atype.Kind())fmt.Println("value in atype is: ", atype)}
结果是:
kind of atype is: ptrkind of atype.Elem() is: structvalue in atype is: {3 vs 8.99}
如果我们把
var atype = reflect.ValueOf(&a)
改为
var atype = reflect.ValueOf(a)
func main() {var a = actorix{id: 3, name: "vs", number: 8.99}var atype = reflect.ValueOf(a)fmt.Println("kind of atype is: ", atype.Kind())// 此时要注意这里的atype和a不是一回事,指向的地址不一样,atype是a在堆中的拷贝// 这里编译器会阻止一切修改:// 如果添加代码: atype.FieldByName("id").SetInt(9) ,就会报错 fmt.Println("value in atype is: ", atype)}// 打印结果:// kind of atype is: struct// value in atype is: {3 vs 8.99}
你直接理解为指针就可以了。
应用反射功能写一个简单DI依赖注入容器
首先我们来简单讲一下什么是依赖注入。举个不恰当的例子:
某位老板想要做假账骗钱,于是对财务总监说:你把账调整一下,财务总监心领神会于是对手下的老财说:你把账调整一下,老板等着要!
很显然上述过程中,老板最终需要的是老财调整好的账本,但他不会直接指挥老财,而是下达命令给财务总监。老板本人是不会做账,他等着财务总监给他反馈;而财务总监也是不做账的,他等着老财给他反馈。
老板想要的结果依赖于财务总监的执行情况,财务总监想要的结果依赖于老财的执行结果。
这就是依赖注入想要解决的问题,在现实大型程序中,任务链可能非常长,如果企业层层上报的体制一样,一个类的实现可能依赖于很多调用链上的其他类的实现,但我们只想做老板,只关心本层的实现,并不想关心下一层会怎么做,就像我们例子中老板并不在意这件调账这项工作到底是财务总监做还是财务总监手下的老财做,所以我们需要一个机制,帮助我们一旦发出调用某个类的指令,就会事先自动调用这个类的实现,这同样可以理解为控制反转。
我们先来做一个最简单的基础原型,为了简便起见我们只设定两层依赖(从财务总监发出order到老财),然后把依赖注入全部放到一个diInvoke()
函数当中:
// Accountant 我们先设计一个老财结构体type Accountant struct {name string}// 初始化Account结构体的函数,其返回值为Accountantfunc ctorAccountant() Accountant {return Accountant{name: "十年老会计"}}// order命令函数,传入参数是Account,我们需要一个指挥老财帮我们做账// order的调用依赖于Accountant结构体的构建func order(a Accountant) {fmt.Println("需要去做假账的老财是: ", a.name)}// 这是我们的依赖注入函数,我们预期的效果是,他传入一个函数类型的参数(也就是order函数)// 然后回自动解析order的形参(也就是Accountant结构体)// 根据这个形参类型自动去一张dimap中寻找返回值为该形参类型的函数(也就是ctorAccountant函数)// 然后调用该函数,并将结果保存在一张形参数组中,此时order函数所需要的形参已经构建完毕// 最后调用order函数本身// 完毕func diInvoke(function interface{}) error {// 新建一张dimap我们要将ctorAccountant函数注册进去// dimap的key是ctorAccountant返回值的reflect.Type// dimap的value是ctorAccountant本身的reflect.Value(也可以理解为reflect.Value结构体模式的函数指针)var dimap = make(map[reflect.Type]reflect.Value)var funcs = ctorAccountant// 通过ValueOf函数我们将ctorAccountant转换为Value结构体var t = reflect.ValueOf(funcs)// vt保存ctorAccountant的Type类型var vt = t.Type()// 我们这里使用vt.NumOut()获取所有ctorAccountant函数的返回值数量// 并用vt.Out()函数将所有返回值类型保存在一个results数组中(Type.Out()返回值类型为reflect.Type)var results = make([]reflect.Type, vt.NumOut())for i := 0; i < vt.NumOut(); i++ {// Out函数原型:Out func(i int) Type 返回第i个类型变量的返回值results[i] = vt.Out(i)}// 同理这里处理的是形参,但与返回值的处理不同,形参我们不能保存为reflect.Type的数组// 我们需要保存的是reflect.Value的数组,这是因为后期的函数调用Value.Call()能够接受的参数为[]Valuevar params = make([]reflect.Value, vt.NumIn())for i := 0; i < vt.NumIn(); i++ {// In函数原型:In func(i int) Type,返回类型变量的第i个形参,返回的是一个Type类型,这里转换成ValueOfparams[i] = reflect.ValueOf(vt.In(i))}// 我们这里先分别打印以下形参数组params[]和返回值数组results[]fmt.Println("形参数组为: ", params)fmt.Println("返回值数组为: ", results)// 建立dimap {key: 返回值类型, value: t的reflect.Value形式}// 由于我们知道result中只有一个值,我们这里为了方便起见直接指定了要插入的resultdimap[results[0]] = tfmt.Println("依赖注入表: ", dimap)// 接下来轮到我们对传入的形参处理了,别忘了,本函数的形参function就是order函数// 同样我们需要用reflect.ValueOf获取function本身的reflect.Value结构体var vf = reflect.ValueOf(function)// 先判断其是不是函数类型,不是的话就直接报错if vf.Kind() != reflect.Func {return fmt.Errorf("constructor must be a func")}// 获取order函数的reflect.Type接口形式var vft = vf.Type()// 获取order函数本身的形参列表,并将其放到数组vfparams中,注意数组类型同样是reflect.ValueOfvar vfparams = make([]reflect.Value, vft.NumIn())for i := 0; i < vft.NumIn(); i++ {// 这里是最关键的,dimap中的key是返回值,也就是当前order的形参(a Accountant)// 所以我们可以用dimap[vft.In(i)]获取对应的函数// 然后调用Call(params)直接运行参数生成结构,Call将返回结构包装成一个Value[]数组// 这样一来,我们就已经将function的形参都创建好了vfparams[i] = dimap[vft.In(i)].Call(params)[i]}fmt.Println("形参数组为: ", vfparams)// 直接运行order函数vf.Call(vfparams)return nil}func main() {diInvoke(order)}
总体过程如下:
我们有一个函数func ctorAccountant() Accountant
然后将其注册到一张表dimap里面:
指令函数出现:func order(a Accountant)
指令函数需要传入的形参类型是Accountant
,于是查表找到键为Accountant
的值
用Call方法调用Accountant
键对应的函数ctorAccountant
将返回的结果ctorAccountant
返回的结果保存在一个vfparams
数组中,依赖完成
调用order函数,将vfparams
的形参传入其中,order函数执行完毕
参考自:码神桃花源的博客。