reflection
- 反射(reflection)是程序在运行时通过检查其定义的变量和值获得对应的真实类型。
在计算机科学领域,反射是指一类应用能够自描述和自控制。此类应用采用某种机制来实现自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
- 反射机制就是在运行时动态的调用对象的方法和属性
每种编程语言的反射模型都不同,有些语言并不支持反射。支持反射的语言可以在程序编译期将变量的反射信息,比如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样可在程序运行期获取类型的反射信息,并有能力改变它们。
- 反射是指在程序运行期对程序本身进行访问和修改的能力
程序在编译时变量会被转换为内存地址,而变量名不会被编译器写入到可执行部分,因此在运行程序时程序是无法获取自身信息的。
reflect
Golang提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但在编译时并不知道这些变量的具体类型,这种机制称为反射。
反射可将类型本身作为第一类型的值来处理
- Golang程序在运行期可使用
reflect
包访问程序的反射信息
Golang提供了reflect
包用来实现反射,通过refelct
包能够完成对一个interface{}
空接口变量的具体类型和值进行获取。
Golang程序的反射系统无法获取到一个可执行文件空间中或一个包中的所有类型信息,需配合标准库中对应的此法、语法解析器和抽象语法树(AST)对源码进行扫描后获取。
interface
- Golang关于类型设计的原则
变量包括(value, type)
两部分,type
包括static type
和concrete type
,简单来说static type
是在编码时可见的类型,concrete type
是runtime
系统可见的类型。
类型断言能够成功取决于变量的concrete type
而非static type
,比如一个变量的concrete type
如果实现了write
方法则可被类型断言为writer
。
- 反射是建立在类型之上的
Golang指定类型的变量是static
的,因此在创建变量时就已经确定。反射主要与Golang的interface
类型相关,因为interface
的类型是concrete
的,只有interface
类型才有反射一说。
-
interface
与pair
的存在是Golang中实现反射的前提
在Golang的实现中每个interface
变量都拥有一对pair(value, type)
,pair
中记录了实际变量的值和类型(value, type)
。
value
是实际变量的值
type
是实际变量的类型
一个interface{}
类型的变量包含两个指针:
一个指针下指向实际的值,即对应的value
。
一个指针指向值的类型,对应concrete type
。
例如:创建类型为*os.File
的变量
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
将变量赋值给一个接口变量
var r io.Reader
r = tty
赋值后接口变量的pair
中记录的信息为(tty, *os.File)
,这个pair
在接口变量连续赋值过程中是不会改变的。
比如将接口变量赋值给另一个接口变量
var w io.Writer
w = r.(io.Writer)
此时接口变量w的pair和r的pair相同,都是(tty, *os.File)
。即使w是空接口类型,pair
也是不变的。
方法
Golang中反射是由reflect
包支持的,reflect
包提供了两个重要的类型Type
和Value
。任意接口值在反射中都可以理解为由reflect.Type
和reflect.Value
两部分组成。
反射是用来检测存储在接口变量内部(值 value
, 类型 concrete type
)pair
对的一种机制,reflect
包提供了reflect.TypeOf
和reflect.ValueOf
两个函数来获取任意对象的Value
和Type
。
-
reflect.ValueOf
用于获取输入参数接口中的数据的值,若接口为空则返回0。
func reflect.ValueOf(i interface{}) reflect.Value
-
reflect.TypeOf
用于动态获取输入参数接口中值的类型,若接口为空则返回nil
。
func reflect.TypeOf(i interface{}) reflect.Type
reflect.TypeOf()
用于获取pair(value, type)
中的type
,reflect.ValueOf()
获取获取pair(value, type)
中的value
。
例如:将float64
类型的变量作为原值传递给reflect.TypeOf(i interface{})
获取其类型信息
var num float64 = 3.14156
t.Log(reflect.ValueOf(num)) //3.14156
t.Log(reflect.TypeOf(num)) //float64
例如:
type Response struct {
Code int
Message string
}
func TestReflect(t *testing.T) {
var response Response
t.Log(reflect.ValueOf(response)) //{0 }
t.Log(reflect.TypeOf(response)) //test.Response
}
定律
反射三大定律
- 反射可以将【接口类型变量】转换为【反射类型对象】
- 反射类型对象指的是
reflect.Type
和reflect.Value
- 反射提供了一种允许程序在运行时检查接口变量内部存储的
pair(value, type)
对的机制 -
reflect
包中的Value
和Type
类型时访问接口内部的数据成为可能 -
reflect
包为Value
和Type
类型分别提供了reflect.ValueOf()
和reflect.TypeOf()
方法来进行读取
- 反射可以将【反射类型对象】转换为【接口类型变量】
- 和物理学中的反射类似,Golang中的反射也能够创造自己反面类型的对象。
- 若要修改【反射类型对象】则其值必须可写
- 可通过
value.CanSet()
方法来检查一个reflect.Value
类型的变量是否具有“可写性” - 可写性类似于寻址能力,但更为严格。它是反射类型变量的一种属性,会赋予该变量修改底层存储数据的能力。
- 可写性最终是由一个反射对象是否存储原始值而决定的
结构体
- 使用反射获取结构体中的字段信息
通过reflect.TypeOf()
从结构体中获取反射类型对象reflect.Type
,可通过reflect.Type
获取结构体成员信息。
结构体成员访问方法列表,与成员获取相关的reflect.Type
的方法。
方法 | 返回值 | 描述 |
---|---|---|
type.Field(index int) | StructField | 根据索引获取对应结构体字段 |
type.NumField() | int | 获取结构体成员字段数量 |
type.FieldByName(name string) | (StructField, bool) | 根据指定字符串获取对应的结构体字段 |
type.FieldByIndex(index []int) | StructField | 多层成员访问,根据[]int提供的结构体字段索引获取字段信息。 |
type.FieldByNameFunc(match func(string) bool) | (StructField, bool) | 根据匹配函数获取字段 |
例如:通过反射值对象reflect.Value
获取结构体成员信息
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
}
func main() {
user := &User{Id: 1, Name: "root"}
val := reflect.ValueOf(user)
fmt.Printf("%v\n", val) //&{1 root}
elem := val.Elem()
fmt.Printf("%v\n", elem) //{1 root}
elemType := elem.Type()
fmt.Printf("%v\n", elemType) //main.User
numField := elem.NumField()
fmt.Printf("%v\n", numField) //2
for i := 0; i < numField; i++ {
fieldName := elemType.Field(i).Name
field := elem.Field(i)
fmt.Printf("%v\n", field)
fieldType := field.Type()
fieldValue := field.Interface()
fmt.Printf("%d: %s %s = %v\n", i, fieldName, fieldType, fieldValue)
}
}
&{1 root}
{1 root}
main.User
2
1
0: Id int = 1
root
1: Name string = root
reflect.StructField
- 结构体字段类型
reflect.Type
的Field()
方法返回StructField
结构,描述了结构体的成员信息,可获取成员与结构体的关系,比如偏移、索引、是否为匿名字段、结构体标签等。
type StructField struct{
Name string //字段名
PkgPath string //字段路径
Type Type //字段反射类型对象
Tag StructTag//字段的结构体标签
Offset uintptr //字段在结构体中的相对偏移
Index []int //Type.FieldByIndex返回的索引值
Anonymous bool//是否为匿名字段
}
字段 | 类型 | 描述 |
---|---|---|
Name | string | 字段名称 |
PkgPath | string | 字段在结构体中的路径 |
Type | Type | 字段本身的反射类型对象,类型为reflect.Type。 |
Tag | StructTag | 结构体标签,为结构体字段标签的额外信息,可单独提取。 |
Index | FieldByIndex | 索引顺序 |
Anonymous | bool | 字段是否为匿名字段 |
- 获取结构体成员反射信息
例如:实例化结构体后遍历成员,通过reflect.Type
的FieldByName()
获取字段信息。
//声明结构体
type User struct {
Id int
Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//遍历结构体成员
for i := 0; i < typ.NumField(); i++ {
//获取成员
field := typ.Field(i)
//获取成员属性
name := field.Name
tag := field.Tag
fmt.Printf("name = %v, tag = %v\n", name, tag)
}
//声明结构体
type User struct {
Id int
Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
tag := field.Tag
tagName := tag.Get("json")
tagId := tag.Get("id")
fmt.Printf("name = %v, id = %v\n", tagName, tagId) //name = name, id = 100
}
reflect.StructTag
- 结构体标签
通过reflect.Type
获取结构体成员reflect.StructField
结构中的Tag
即结构体标签StructTag
。
结构体标签是对结构体字段的额外信息标签
JSON、BSON等格式进行序列化,ORM关系对象映射都会使用到结构体标签,使用标签设定字段在处理时应具备的特殊属性和可能发生的行为。这些信息都是静态的,无需实例化结构体可通过反射获取到。
结构体标签书写格式:key1:"value1" key2:"value2"
结构体标签由一个或多个键值对组成,键与值使用冒号分割,值使用双引号包裹,键值对之间使用空格分割。
从结构体标签中获取值
StructTag
拥有方法可以对Tag
信息进行解析和提取
- Get方法根据Tag中的键获取对应的值
func (tag StructTag) Get(key string) string
- Lookup方法根据Tag中的键查询值是否存在
func (tag StructTag) Lookup(key string) (value string, ok bool)
例如:
//声明结构体
type User struct {
Id int `json:"id" bson:"id"`
Name string `json:"name" bson:"name" orm:"size(32)"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
tag := field.Tag
val, ok := tag.Lookup("bson")
if ok {
fmt.Printf("bson = %v\n", val) //bson = name
}
val = tag.Get("json")
fmt.Printf("json = %v\n", val) //json = name
}
反射值对象
reflect.ValueOf
func reflect.ValueOf(rawValue interface{}) reflect.Value
例如:
val := reflect.ValueOf(nil)
fmt.Printf("%v\n", val) //<invalid reflect.Value>
例如:动态调用无参数的结构体方法
package test
import (
"fmt"
"reflect"
"testing"
)
type User struct {
Id int
Name string
}
func (u *User) Do() {
fmt.Printf("User Do...\n")
}
func TestReflect(t *testing.T) {
ins := &User{Id: 1, Name: "root"}
val := reflect.ValueOf(ins)
fname := "Do"
val = val.MethodByName(fname)
val.Call(nil)
}
例如:动态调用结构体带参数的方法
package test
import (
"fmt"
"reflect"
"testing"
)
type User struct {
Id int
Name string
}
func (u *User) Do(id int, msg string) {
fmt.Printf("%v %v\n", id, msg)
}
func TestReflect(t *testing.T) {
ins := &User{Id: 1, Name: "root"}
val := reflect.ValueOf(ins)
fname := "Do"
val = val.MethodByName(fname)
id := reflect.ValueOf(1)
msg := reflect.ValueOf("hello")
in := []reflect.Value{id, msg}
val.Call(in)
}
reflect.Value
-
reflect.ValueOf(rawValue interface)
原值会转换为类型为reflect.Value
的反射值对象。
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
-
reflect.Value
反射值对象可与原值通过包装和值获取相互转化
通过reflect.Value
重新获取原始值
方法 | 返回值 | 描述 |
---|---|---|
Interface() | interface{} | 通过类型断言转换为指定类型 |
Int() | int64 | 将值以int类型返回 |
Uint() | uint64 | 将值以uint类型返回 |
Float() | float64 | 将值以双精度类型返回 |
Bool() | bool | 将值以bool类型返回 |
Bytes() | []byte | 将值以字节数组类型返回 |
String() | string | 将值以字符串类型返回 |
例如:
var i int = 1024
val := reflect.ValueOf(i)
var j int = val.Interface().(int)
fmt.Printf("j = %v\n", j)
var k int = int(val.Int())
fmt.Printf("k = %v\n", k)
- 反射值对象
reflect.Value
对零值和空进行有效性判断
方法 | 返回值 | 描述 |
---|---|---|
value.IsNil () | bool | 判断返回值是否为nil
|
value.IsValid() | bool | 判断返回值是否合法 |
func (v Value) IsNil() bool
若值类型不是通道、函数、接口、映射、指针、切片时会发生panic
,类似语言层面的v == nil
操作。
func (v Value) IsValid() bool
若值本身非法则返回false
例如:判断空指针
var ptr *int
isNil := reflect.ValueOf(ptr).IsNil()
fmt.Printf("%v\n", isNil) //true
例如:判断nil
是否非法
isValid := reflect.ValueOf(nil).IsValid()
fmt.Printf("%v\n", isValid) //false
- 通过反射修改变量的值
使用reflect.Value
对包装的值进行修改时,需遵循一定规则,若没有按照规则进行代码设计和编写,则无法修改对象的值或运行时发生宕机。
反射值对象的判定及获取元素的方法
方法 | 返回值 | 描述 |
---|---|---|
value.Elem() | reflect.Value | 获取值指向的元素值,类似* 操作,当值类型不是指针或接口时会发生宕机,空指针返回nil 的Value 。 |
value.Addr() | reflect.Value | 对可寻址的值返回其地址,类似& 操作,但值不可寻址时发生宕机。 |
value.CanAddr() | bool | 值是否可寻址 |
value.CanSet() | bool | 值是否可被修改,要求值可寻址且是可导出的字段。 |
反射类型对象
reflect.Type
根据传入的结构体生成SQL语句
func BuildInsertSQL(obj interface{}) string {
val := reflect.ValueOf(obj)
typ := reflect.TypeOf(obj)
//判断参数是否为结构体
if val.Kind() != reflect.Struct {
panic("unsupported type")
}
//获取结构体名作为数据表名
typename := typ.Name()
//遍历结构体字段
var cols []string
var vals []string
for i := 0; i < val.NumField(); i++ {
//获取字段名称
col := typ.Field(i).Name
cols = append(cols, fmt.Sprintf("`%s`", col))
//获取字段值
var v string
field := val.Field(i)
switch field.Kind() {
case reflect.String:
v = field.String()
case reflect.Int:
v = strconv.FormatInt(field.Int(), 10)
default:
}
vals = append(vals, v)
}
//转换为字符串
colstr := strings.Join(cols, ",")
valstr := strings.Join(vals, ",")
//拼接SQL
query := fmt.Sprintf("INSERT INTO `%s`(%s) VALUES(%s)", typename, colstr, valstr)
return query
}
type User struct {
id int
name string
}
user := User{id: 1, name: "admin"}
sql := BuildInsertSQL(user)
fmt.Println(sql)
INSERT INTO `User`(`id`,`name`) VALUES(1,admin)
reflect.Elem
- 指针与指针指向的元素
func (v reflect.Value) Elem() reflect.Value
Golang对指针获取反射对象时,通过reflect.Elem()
方法获取指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*
操作。
Golang反射中对指针变量的种类都是Ptr
package test
import (
"fmt"
"reflect"
"testing"
)
//声明空结构体
type User struct {
}
func TestReflect(t *testing.T) {
//创建指针变量
ins := &User{}
//获取指针变量的反射类型信息
typ := reflect.TypeOf(ins)
//获取指针变量的类型名称
name := typ.Name()
//获取指针变量的种类
kind := typ.Kind()
fmt.Printf("name = %v, kind = %v\n", name, kind) //name = , kind = ptr
//获取指针变量的元素类型
elem := typ.Elem()
//获取指针类型元素的名称
name = elem.Name()
//获取指针类型元素的种类
kind = elem.Kind()
fmt.Printf("name = %v, kind = %v\n", name, kind) //name = User, kind = struct
}
value.Elem()
方法会返回反射值对象所包含的值或指针指向的值,若反射值对象的种类Kind
不是接口或指针,则会发生panic
。若反射值为0则返回零值。
ptr := (*int)(nil)
val := reflect.ValueOf(ptr)
fmt.Printf("%v\n", val) //<nil>
elem := val.Elem()
fmt.Printf("%v\n", elem) //<invalid reflect.Value>
isValid := elem.IsValid()
fmt.Printf("%v\n", isValid) //false
reflect.Indirect
func reflect.Indirect(v reflect.Value) reflect.Value
Golang的reflect.Indirect()
函数用于获取v
指向的值,若v
为nil
指针则返回零值,若v
不为指针返回为v
。
与reflect.Elem()
方法不同的是,Elem()
方法当调用者的Kind
种类不是Interface
接口或Ptr
指针类型时,会报panic
。如果调用者为0,则返回零值。Indirect()
的参数如果为nil
指针时会返回零值。
当调用者或参数reflect.Value
为指针时,value.Elem()
等效于reflect.Indirect(value)
。若不是指针则并非等效。
reflect.Indirect()
用于需要接受待定类型或指向该类型的指针。
reflect.Kind
编程中使用最多的是类型(Type),反射中当需要区别一个大品种的类型时使用种类(Kind)。
例如:需要统一判断类型中的指针时,使用种类(Kind)较为方便。
Golang中类型(Type)是指系统原生的数据类型,比如int
、string
、bool
、float32
等,以及使用type
关键字定义的类型,这些类型的名称就是其类型本身的名称。
种类(Kind)指的是对象归属的品种
reflect
包中定义的种类Kind
type Kind uint
种类常量 | 描述 |
---|---|
Invalid Kind | 非法类型 |
Bool | 布尔型 |
Int | 有符号整型 |
Int8 | 有符号8位整型 |
Int16 | 有符号16位整型 |
Int32 | 有符号32位整型 |
Uint | 无符号整型 |
Uint8 | 无符号8位整型 |
Uint16 | 无符号16位整型 |
Uint32 | 无符号32位整型 |
Uint64 | 无符号64位整型 |
Uintptr | 指针 |
Float32 | 单精度浮点数 |
Float64 | 双精度浮点数 |
Complex64 | 64位复数类型 |
Complet128 | 128位复数类型 |
Array | 数组 |
Chan | 通道 |
Func | 函数 |
Interface | 接口 |
Map | 映射 |
Ptr | 指针 |
Slice | 切片 |
String | 字符串 |
Struct | 结构体 |
UnsafePointer | 底层指针 |
Map、Slice、Chan属于引用类型,使用起来类似指针,但在种类中仍属于独立的种类,而不属于Ptr。
例如:
type User struct{}
User结构体属于Struct种类,*User属于Ptr种类。
从类型对象reflect.Type
中获取类型名称Name
和种类Kind
- 类型名称对应的反射获取方法是
reflect.Type
中的Name()
方法,返回表示类型名称的字符串。 - 类型归属的种类
Kind
使用的是reflect.Type
中的Kind()
方法,返回reflect.Kind
类型的常量。
例如:从常量中获取类型信息
package test
import (
"fmt"
"reflect"
"testing"
)
//定义枚举类型
type Enum int
//定义常量
const (
Zero Enum = 0
)
func TestReflect(t *testing.T) {
typ := reflect.TypeOf(Zero)
name := typ.Name() //Enum
kind := typ.Kind() //int
fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Enum, Kind = int
}
例如:从结构体中获取类型信息
package test
import (
"fmt"
"reflect"
"testing"
)
type Response struct {
Code int
Message string
}
func TestReflect(t *testing.T) {
//结构体实例
response := Response{}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(response)
//获取反射类型对象的名称和种类
name := typ.Name() //Response
kind := typ.Kind() //struct
fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Response, Kind = struct
}
type.Implements
- 判断实例是否已实现接口
创建接口
type IT interface {
test()
}
创建结构体并实现接口方法
type T struct {
}
func (t *T) test() {
fmt.Printf("T struct test run...\n")
}
判断结构体是否实现接口
t1 := reflect.TypeOf(&T{})
t2 := reflect.TypeOf((*IT)(nil)).Elem()
ok := t1.Implements(t2)
fmt.Printf("%v\n", ok)
reflect.Addr
func (v reflect.Value) reflect.Addr() reflect.Value
Golang中的reflect.Addr()
函数用于获取表示v
地址的指针值
reflect.New
func reflect.New(typ reflect.Type) reflect.Value
reflect.SliceOf
func reflect.SliceOf(t reflect.Type) reflect.Type
- reflect.SliceOf() 函数用于获取元素类型为
t
的切片类型,如果t
表示int
,SliceOf(t)
表示[]int
。
value.Call
- 反射中函数和方法的调用
func (v reflect.Value) Call(in []reflect.Value) []reflect.Value
- 反射中调用函数
例如:Golang中函数类型可以像普通类型等类型变量作为值来使用
func test(){
fmt.Println("fn running....")
}
func main(){
fn := test
fn()//fn running....
}
既然函数可以像普通类型变量一样,那么在反射机制中和不同的变量是一样的。
反射中函数和方法的类型(Type)都是reflect.Func
,若需调用函数可通过Value
的Call()
方法实现。
func test(){
fmt.Println("fn running....")
}
func main(){
fn := test
rv := reflect.ValueOf(fn)
if rv.Kind() != reflect.Func{
fmt.Println("INVIDAD TYPE")
}
rv.Call(nil)
}
reflect.Value
的Call()
方法的参数是一个[] reflect.Value
切片,对应的反射函数类型的类型,返回值也是一个reflect.Value
的[] reflect.Value
切片,同样对应反射函数类型的返回值。
func test(i int) string{
fmt.Printf("fn running.... %d\n", i)
return strconv.Itoa(i)
}
func main(){
fn := test
//判断类型
rv := reflect.ValueOf(fn)
if rv.Kind() != reflect.Func{
fmt.Println("INVALID TYPE")
}
//设置参数
args := make([]reflect.Value, 1)
args[0] = reflect.ValueOf(100)
//调用函数
vs := rv.Call(args)
fmt.Printf("%v\n", vs)//[100]
fmt.Printf("%v\n", vs[0])//100
rs := vs[0].Interface().(string)
fmt.Printf("%v\n", rs)//100
}
- 反射中调用方法
反射中方法的调用,函数和方法本质上是相同的,只不过方法与一个对象进行了绑定,方法是对象的一种行为,这种行为是对于这个对象的一系列操作。
type Users struct{
id int
name string
}
func (u *Users) SetId(id int){
u.id = id
}
func (u *Users) SetName(name string){
u.name = name
}
func (u *Users) String() string{
return fmt.Sprintf("%p", u) + " name:"+u.name + " id:" + strconv.Itoa(u.id)
}
func main(){
u := &Users{1, "root"}
rv := reflect.ValueOf(&u).Elem()
vs := rv.MethodByName("String").Call(nil)
fmt.Printf("%v\n", vs[0])//0xc000004480 name:root id:1
args := make([]reflect.Value, 1)
args[0] = reflect.ValueOf(100)
vs = rv.MethodByName("SetId").Call(args)
fmt.Printf("%v\n", vs)//[]
args[0] = reflect.ValueOf("admin")
vs = rv.MethodByName("SetName").Call(args)
fmt.Printf("%v\n", vs)//[]
vs = rv.MethodByName("String").Call(nil)
fmt.Printf("%v\n", vs[0])//0xc000004480 name:admin id:100
}