序言
第一次接触反射技术是在很多年前学习设计模式的时候,那时在优化Java版简单工厂的实现,当读取配置信息中的的类型字符串后利用反射来创建对象实例,替代了switch case语句的分支判断。
第二次接触反射技术是在几年前微服务架构开始大范围流行的时候,那时在考虑异构微服务的多版本集成问题,支持反射的语言(Java等)的序列化就容易很多,而不支持反射的语言(C++等)的序列化就麻烦一些,不过最后统一选择了序列化工具protobuf,支持跨语言。
第三次接触反射技术是在听某个团队Session的时候,那时在简单分享Golang的反射机制,发现很多同学都没什么感觉,主要原因是大家在产品代码中几乎不会使用到反射技术。
在生活中,反射像一面镜子,通过镜子你可以清楚的看见自己的一切。
在计算机中,反射表示程序能够检查自身结构的一种能力,尤其是类型。通过反射,你可以获取对象类型的详细信息,并可动态操作对象。
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
因为反射建立在类型系统之上,所以我们先从基础知识开始:)
类型系统
顾名思义,类型系统是指一个语言的类型体系结构,是一门编程语言的地基。一个典型的类型系统统筹包括如下几本内容:
- 基本类型,如int,bool,float,string等
- 复合类型,如数组,结构体,指针等
- Any类型
- 值语义和引用语义
- 类
- 接口
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是整个类型系统的基石:
- 非侵入式:一个类只要实现了接口要求的所有函数,就说这个类实现了该接口
- 接口赋值:既可以将对象实例赋值给接口,也可以将一个接口赋值给另一个接口
- 接口查询:可以直接了当地询问接口指向的对象实例是否实现了另一个接口
- 类型查询:可以直接了当地询问接口指向的对象实例的类型
- 接口组合:可以认为接口组合式类型匿名组合的一个特殊场景
- Any类型:任意类型都实现了零个或多个方法,那就是说任意类型都实现了空接口interface{},即interface{}是可以指向任意对象的Any类型
反射模型
(value, type)
经过学习和实践,我们将Golang中的反射模型形式化表达如下:
interface --> (value, type)
(value, type) --> (value, kind)
(value, type) --> interface
(value, type) --> (value1, type)
interface --> (value, 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) --> (value, kind)
(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
)
(value, type) --> interface
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。
(value, type) --> (value1, type)
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
如果想修改结构体中成员的值时,则
- v.CanSet()必须为true
- 该成员名必须大写(可导出)
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.Newreflect.Value对象
reflect.MakeFunc
注:为了简单起见,例子中的匿名函数只关注返回值,而对参数不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
}
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
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反射模型有一定的帮助。