第一次接触反射技术是在很多年前学习设计模式的时候,那时在优化Java版简单工厂的实现,当读取配置信息中的的类型字符串后利用反射来创建对象实例,替代了switch case语句的分支判断。
第二次接触反射技术是在几年前微服务架构开始大范围流行的时候,那时在考虑异构微服务的多版本集成问题,支持反射的语言(Java等)的序列化就容易很多,而不支持反射的语言(C++等)的序列化就麻烦一些,不过最后统一选择了序列化工具protobuf,支持跨语言。
第三次接触反射技术是在听某个团队Session的时候,那时在简单分享Golang的反射机制,发现很多同学都没什么感觉,主要原因是大家在产品代码中几乎不会使用到反射技术。
在生活中,反射像一面镜子,通过镜子你可以清楚的看见自己的一切。
在计算机中,反射表示程序能够检查自身结构的一种能力,尤其是类型。通过反射,你可以获取对象类型的详细信息,并可动态操作对象。
众所周知,大多数语言都支持反射,但每种语言的反射模型却不尽相同。笔者在这一年多的Golang实践中,学习或二次开发了很多个框架,其中大部分都涉及反射技术,比如GoConvey,GoMock,GoStub,Monkey和gRPC等,所以本文的主角是Golang反射模型
。
我们在讨论反射模型之前,先看看反射应用的一个简单案例:
// Stubs represents a set of stubbed variables that can be reset.
type Stubs struct {
// stubs is a map from the variable pointer (being stubbed) to the original value.
stubs map[reflect.Value]reflect.Value
origEnv map[string]envVal
}
// Stub replaces the value stored at varToStub with stubVal.
// varToStub must be a pointer to the variable. stubVal should have a type
// that is assignable to the variable.
func (s *Stubs) Stub(varToStub interface{}, stubVal interface{}) *Stubs {
v := reflect.ValueOf(varToStub)
stub := reflect.ValueOf(stubVal)
// Ensure varToStub is a pointer to the variable.
if v.Type().Kind() != reflect.Ptr {
panic("variable to stub is expected to be a pointer")
}
if _, ok := s.stubs[v]; !ok {
// Store the original value if this is the first time varPtr is being stubbed.
s.stubs[v] = reflect.ValueOf(v.Elem().Interface())
}
// *varToStub = stubVal
v.Elem().Set(stub)
return s
}
// Reset resets all stubbed variables back to their original values.
func (s *Stubs) Reset() {
for v, originalVal := range s.stubs {
v.Elem().Set(originalVal)
}
s.resetEnv()
}
上面的代码来自GoStub框架,Stub方法可以连续对多个全局变量或函数变量打桩,Reset方法对所有全局变量或函数变量的桩进行回滚。
Stub方法有两个参数,第一个参数是变量的地址,第二个参数是变量的桩。这两个参数的类型都是空接口,在方法处理的开始都转换成了reflect.Value类型。然而,无论怎么转换,第一个参数必须是指针,因为最终目标是要改变指针指向的值,所以要进行“址传递”。当指针校验不通过时(v.Type().Kind() != reflect.Ptr),就会触发panic。Stub方法先在map中保存变量指针和变量初始值,然后修改变量指针指向的值为桩。Reset方法将map中保存的所有变量指针指向的值修改为初始值。
这就是反射应用的一个简单案例。大家先了解大体意思就行,不用太纠结技术细节,等理解反射模型后,就会发现it is a piece of cake
。
因为反射建立在类型系统之上,所以我们先从基础知识开始:)
顾名思义,类型系统是指一个语言的类型体系结构,是一门编程语言的地基。一个典型的类型系统统筹包括如下几本内容:
Golang与Python不同,变量类型是静态的,即变量在创建的时候类型就已经确定。
我们对变量做如下声明:
type MyInt int
var i int
var j MyInt
变量i的类型是int,而变量j的类型是MyInt,虽然底层类型都是int,但是它们的静态类型并不一样,当不经过类型转换直接相互赋值时,编译器会报错。
Golang中的指针相对C/C++语言进行了大量的简化,没有”强大“的指针计算功能,仅仅代表变量的地址,属于复合类型。当指针类型作为函数参数类型时,为“址传递”。
Golang中有四种类型比较特殊,为引用类型,分别为slice,map,channel和interface。当引用类型作为函数参数类型时,为“引用传递”,只能使modify操作生效,而add操作不能生效。要想使add操作生效,必须使用“址传递”,所以json.Unmarshal时引用类型的变量也要进行“址传递”。反过来说,如果没有add操作,则就没有必要进行“址传递”,“引用传递”的效率也很高。
其他类型作为函数参数类型时均为“值传递”。
在Golang中,可以给任意自定义类型添加相应的方法,指针类型除外。
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
Integer和int属于不同的静态类型,更重要的是Integer可以像普通的类一样使用:
func main() {
var a Integer = 1
if a.Less(2) {
fmt.Println(a, "Less 2")
}
}
Golang中的struct和其它语言中的class有同等重要的地位,但是Golang放弃了包括继承在内的大量面向对象特性,通过组合来解决所有问题(通过匿名组合来模拟继承的一部分功能)。
Golang中的interface是整个类型系统的基石:
在Golang的实现中,每个interface变量都有一个对应的pair,pair中记录了实际变量的值和类型,即(value, type)
。在基本的层面上,反射只是一个检查存储在interface变量中的value和type的算法,value和type用类型reflect.Value和reflect.Type描述。
尽管从reflect.Value也很容易得到reflect.Type(reflect.Value.Type()),但为了让value和type在概念上进行分离,我们更习惯用pair进行表达。
经过学习和实践,我们将Golang中的反射模型形式化表达如下:
interface --> (value, type)
(value, type) --> (value, kind)
(value, type) --> interface
(value, type) --> (value1, type)
定义类File:
type File struct {
...
}
func (f *File) Read(buf []byte) (n int, err error) {
...
}
func (f *File) Write(buf []byte) (n int, err error) {
...
}
func (f *File) Seek(off int64, whence int) (pos int64, err error) {
...
}
func (f *File) Close() error {
...
}
定义接口Reader,Writer和Closer:
type Reader interface {
Read(buf []byte) (n int, err error)
}
type Writer interface {
Write(buf []byte) (n int, err error)
}
type Closer interface {
Close() error
}
尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类却实现了这些接口:
f := &File{}
var r Reader
r = f
var w Writer
w = r.(Writer)
var c Closer
c = w.(Closer)
空接口可以直接赋值:
var e interface{}
e = c
上面代码中,r,w,c和e的pair均为(f, *File)。pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。
如何从接口变量中获取value和type信息?
通过reflect.ValueOf函数获取value,通过reflect.TypeOf函数获取type:
package main
import (
"fmt"
"reflect"
)
type MyInt int
func main() {
var i MyInt = 10
fmt.Println("value: ", reflect.ValueOf(i))
fmt.Println("type: ", reflect.TypeOf(i))
}
运行时输出的结果是:
value: 10
type: main.MyInt
可见type中描述的是静态类型。
(value, type)中的type描述的是静态类型,那如何才能知道底层类型?
reflect.Type有一个Kind方法,而通过Kind方法返回一个常量来表示底层类型:
package main
import (
"fmt"
"reflect"
)
type MyInt int
func main() {
var i MyInt = 10
fmt.Println("type: ", reflect.TypeOf(i))
fmt.Println("type.Kind: ", reflect.TypeOf(i).Kind())
}
运行时输出的结果是:
type: main.MyInt
type.Kind: int
底层类型在reflect包中有详细的定义:
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
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
Fun
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
reflect.Value可以使用Interface方法还原接口值,该方法高效的打包类型和值信息到接口值中,并返回这个结果:
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}
当得到一个类型为reflect.Value的变量,可以通过下面的方式转换变量的类型:
t := v.Interface().(T)
当类型查询成功后,t就可以使用T的成员和方法。
reflect.Value除过能直接转换成Interface{},还可以直接转换成基本类型,比如:
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Int() int64
func (v Value) Float() float64
...
以Int方法为例,如果v.Kind()不是Int, Int8, Int16, Int32, 活着Int64,会触发panic。
reflect.Value变量是通过reflect.Value(n)获得的,n可能是值也可能是指针。
当n是值时,不能通过reflect.Value修改n的值:
n := 3.4
v := reflect.ValueOf(n)
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(4.1)
运行上面的代码,结果如下:
settability of v: false
panic: reflect: reflect.Value.SetFloat using unaddressable value
当n是指针时,可以通过reflect.Value修改n指向的变量的值:
m := 3.4
n := &m
p := reflect.ValueOf(n)
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(4.1)
fmt.Println("m: ", m)
运行上面的代码,结果如下:
settability of v: true
m: 4.1
对于结构的反射操作并没有根本的不同,只是用了reflect.Value和reflect.Type的Field方法按索引获取对应的成员,比如:
type Student struct {
Id int
Name string
}
s := Student{100032, "zxl"}
v := reflect.ValueOf(s)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
t.Field(i).Name, f.Type(), f.Interface())
}
运行上面的代码,结果如下:
0: Id int = 100032
1: Name string = zxl
如果想修改结构体中成员的值时,则
type Student struct {
Id int
Name string
}
s := Student{100032, "zxl"}
v := reflect.ValueOf(&s).Elem()
t := v.Type()
v.Field(0).SetInt(230001)
v.Field(1).SetString("lxz")
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
t.Field(i).Name, f.Type(), f.Interface())
}
运行上面的代码,结果如下:
0: Id int = 230001
1: Name string = lxz
假设函数FuncReturing的功能是创建一个匿名函数:
func FuncReturning(funcType reflect.Type, results ...interface{}) reflect.Value {
var resultValues []reflect.Value
for i, r := range results {
var retValue reflect.Value
if r == nil {
retValue = reflect.Zero(funcType.Out(i))
} else {
tempV := reflect.New(funcType.Out(i))
tempV.Elem().Set(reflect.ValueOf(r))
retValue = tempV.Elem()
}
resultValues = append(resultValues, retValue)
}
return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
return resultValues
})
}
函数FuncReturing的第一个参数funcType用于描述匿名函数的类型,其余参数results是一个变参(本质是数组切片的语法糖),用于匿名函数的返回值的输入:
reflect.ValueOf(nil)
,而是生成一个reflect.Value零值
reflect.ValueOf(r)
,而是先使用reflect.New
生成一个reflect.Value对象
,然后设置这个对象的数据最后通过reflect.MakeFunc
生成一个funcType类型的匿名函数。
注:为了简单起见,例子中的匿名函数只关注返回值,而对参数不care。
如果以上描述的很难理解,先看以下示例:
package main
import (
"fmt"
"reflect"
)
type Temp struct {
date string
Source string
}
func main() {
reflectFun()
}
func reflectFun() {
//创建类型示例
fmt.Println("===========创建类型示例演示=========")
var a Temp
typeOfA:=reflect.TypeOf(a)
fmt.Println(typeOfA)
aIns:=reflect.New(typeOfA)
fmt.Println(aIns.Type(),aIns.Kind(),aIns)
//fmt.Println(aIns.set)
//修改值
fmt.Println("===========修改值示例演示=========")
valueOfDog:=reflect.ValueOf(&Temp{Source:"2018-05-20"})
elem := valueOfDog.Elem()
name := elem.FieldByName("Source")
name.SetString("2019-05-20") //source : 结构体首字符要大写,不然执行此处会发生异常
fmt.Println(name.String())
//判断是否有效
fmt.Println("===========判断是否有效示例演示=========")
s:=Temp{}
fmt.Println(reflect.ValueOf(s).FieldByName("Source").IsValid()) //找到(有效)
fmt.Println(reflect.ValueOf(s).FieldByName("source01").IsValid())//没有找到(无效)
}
func main() {
//定义一个函数,完成数据的交换,具体实现由makeSwap执行
//由于在事先不知道具体的数据类型,因此采用reflect.Value
swap:= func(in []reflect.Value)[]reflect.Value {
return []reflect.Value{in[1],in[0]}
}
//执行数据交换的实现
makeSwap:= func(fPtr interface{}) {
//获取元素
fn:=reflect.ValueOf(fPtr).Elem()
//创建正确类型的函数
//func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
//MakeFunc返回一个具有给定类型、包装函数fn的函数的Value封装。当被调用时,该函数会:
//
//- 将提供给它的参数转化为Value切片
//- 执行results := fn(args)
//- 将results中每一个result依次排列作为返回值
v := reflect.MakeFunc(fn.Type(), swap)
//赋值(1次即可完成所有值的赋值)
fn.Set(v)
}
var intSwap func(int,int)(int,int)
makeSwap(&intSwap)
i1, i2 := intSwap(100, 200)
fmt.Println(i1,i2)
var strSwap func(string,string)(string,string)
makeSwap(&strSwap)
str1,str2:=strSwap("hello","jiangzhou")
fmt.Println(str1,str2)
}
可以看出,代码在不知道具体类型的情况下动态完成了数据的交换。
接下来,再来看看上面提到的函数FuncReturing。
func FuncReturing(funcType reflect.Type,results ...interface{})reflect.Value {
var resultsValue []reflect.Value
for i,r:=range results{
var value reflect.Value
if r==nil {
//Zero返回一个持有类型typ的零值的Value。注意持有零值的Value和Value零值是两回事。Value零值表示不持有任何值。
// 例如Zero(TypeOf(42))返回一个Kind为Int、值为0的Value。Zero的返回值不能设置也不会寻址。
//Out(i int) 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
value=reflect.Zero(funcType.Out(i))//i 为函数对应的第i个参数
}else {
//New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针,返回值的Type为PtrTo(typ)。
tempV:=reflect.New(funcType.Out(i))
tempV.Elem().Set(reflect.ValueOf(r))
value=tempV.Elem()
}
resultsValue= append(resultsValue, value)
}
//MakeFunc返回一个具有给定类型、包装函数fn的函数的Value封装
return reflect.MakeFunc(funcType, func(args []reflect.Value) (results []reflect.Value) {
return resultsValue
})
}
func main() {
var temFun1 func(int,int)(int,int)
fn:=reflect.ValueOf(&temFun1).Elem()
funType:=fn.Type()
v:=FuncReturing(funType,nil,200)
fn.Set(v)
fmt.Println(temFun1(333,666))
}
由此可以看出,通过FuncReturing实现了数据的设置,与函数具体输入的值没有什么关系(333,666)(匿名函数只关注返回值,而对参数不care)
在反射中,函数或方法的底层类型都是reflect.Func。如果要调用函数或方法的话,可以使用reflect.Value的Call方法,参数和返回值类型都是[]reflect.Value。
通过反射调用函数
示例代码:
func prints(i int) string {
fmt.Println("i =", i)
return strconv.Itoa(i)
}
func main() {
v := reflect.ValueOf(prints)
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(20)
fmt.Println("result:", v.Call(params)[0])
}
运行上面的代码,结果如下:
i = 20
result: 20
通过反射调用方法
方法和函数可以说在本质上是相同的,只不过方法与一个对象
进行了绑定
,方法是对象
的一种行为,比如:
type Student struct {
id int
name string
}
func (s *Student) SetId(id int) {
s.id = id
}
func (s *Student) SetName(name string) {
s.name = name
}
func (s *Student) String() string {
return "id = " + strconv.Itoa(s.id) + ", " + "name = " + s.name
}
使用reflect.Value的MethodByName
方法:
func main() {
s := &Student{100032, "zxl"}
v := reflect.ValueOf(s)
fmt.Println("Before:", v.MethodByName("String").Call(nil)[0])
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(230001)
v.MethodByName("SetId").Call(params)
params[0] = reflect.ValueOf("lxz")
v.MethodByName("SetName").Call(params)
fmt.Println("After:", v.MethodByName("String").Call(nil)[0])
}
运行上面的代码,结果如下:
Before: id = 100032, name = zxl
After: id = 230001, name = lxz
使用reflect.Value的Method
方法:
func main() {
s := &Student{100032, "zxl"}
v := reflect.ValueOf(s)
fmt.Println("Before:", v.Method(2).Call(nil)[0])
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(230001)
v.Method(0).Call(params)
params[0] = reflect.ValueOf("lxz")
v.Method(1).Call(params)
fmt.Println("After:", v.Method(2).Call(nil)[0])
}
运行上面的代码,结果如下:
Before: id = 100032, name = zxl
After: id = 230001, name = lxz
本文先通过一个简单案例来说明Golang反射技术的价值,接着简单回顾了类型系统,然后详细阐述了Golang反射模型:
interface --> (value, type)
(value, type) --> (value, kind)
(value, type) --> interface
(value, type) --> (value1, type)
最后又加了几道关于进阶话题的菜:
希望对读者理解Golang反射模型有一定的帮助。
文章出处:https://www.jianshu.com/p/c713d0507114(在此文章基础上进行了代码解释、示例的增加)
➢了解更多Go语言知识:https://study.163.com/course/introduction/1210620804.htm