1. 概述
在golang中,reflect是一个比较高级的话题,本文将尽可能简单而又清楚的介绍相关内容。
本文将从如下几个方面进行阐述:
- 什么是reflection ?
- 如何检查一个变量var 并获得其真实类型
- golang中的reflect包
- reflect.Type 和 reflect.Value
- reflect.Kind
- Int() 和 String()
- NumField() 和 Field()
- reflect.Elem
- reflect.StructField
- 完整实例
- 使用reflection原则
2. 什么是reflection
简单的来说反射就是程序在运行时,通过检查其定义的变量以及值,进而找到其对应的真实类型。这样的定义可能很难让人理解,这里不需要担心。
接下来开始让我们根据后面的内容真正的理解reflection(反射)
3. 为什么需要通过检查一个变量并获得其真正的类型
在很多语言中都会存在反射或听说过反射,其中有一个问题在学习reflection(反射)过程中必然产生:为什么在程序运行时需要检查一个变量并获得其对应的类型type?产生的疑问的缘由:
在我们的程序中任意一个定义的变量,其类型都是在编译时是可知的。大多数情况下这样的结论是正确的,但不代表全部。
看看下面的一个简单实例:
package main
import(
"fmt"
)
func main(){
i := 10
fmt.Printf("%d %T",i, i)
}
运行程序输出结果:
10 int
在上面的实例中i的类型其实在编译时已是可知的,当打印出结果时并没有什么神奇的情况出现。
接下来让我们看看运行时的变量类型,首先定义一个function并用一个struct作为形参传递给fmt.Println。
package main
import (
"fmt"
)
type order struct{
ordId int
customerId int
}
func main(){
o := order {
orderId: 1234,
customerId: 567,
}
fmt.Println(o)
}
当运行程序后,输出结果:
{1234 567}
接下来让我们来改造下上面这个例子
package main
import (
"fmt"
)
type order struct{
ordId int
customerId int
}
func query(o order) string{
sql := fmt.Sprintf("insert into order values(%d, %d)",o.ordId, o.customerId)
return sql
}
func maiin(){
o := order{
ordId: 1234,
customerId: 567,
}
fmt.Println(query(o))
}
运行程序输出结果:
insert into order values(1234, 567)
接着我们继续对实例进行改造,让query参数能够适配任意struct:
package main
import (
"fmt"
)
type order struct{
ordId int
customerId int
}
type employee struct{
name string
id int
address string
salary int
country string
}
func query(o interface{}) string{
// 省略具体代码
......
}
func main(){
// 省略代码
......
}
在上面的实例发现我们将query的接收参数类型:interface{}为了接收任意类型的struct。
例如:当我们提供一个order的结构体时:
o := order{
ordId: 1234,
customerId: 567,
}
对应的query返回的输出内容sql:
insert into order values (1234, 567)
那么当我们提供employee的结构体时
e := employee {
name: "Naveen",
id: 565,
address: "Science Park Road, Singapore",
salary: 90000,
country: "Singapore",
}
应该返回的内容sql:
insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
在上面的例子通过提供一个interface{}给query函数能够满足任意struct的处理。(实例也是为了简单起见,提供的struct只包含了string和int类型,不过可以扩展其他类型)
对于query函数来说为了适配任意类型的struct,通过在运行时检查传递的struct的具体类型并根据其包含的字段来生成对应的sql。在这里reflection(反射)是相当有用的。接下来让我们使用golang的reflect包来解决这种情况
4. reflect包
在golang中,reflect包是用来实现运行反射的。通过reflect包能够完成对一个interface{}变量的具体类型以及值的获取。在query函数提供了一个interface{}类型参数并根据interface{}具体的类型以及值生成对应的sql,reflect包对我们来说相对比较需要的并能很好解决该问题。
4.1. reflect.Type和reflect.Value
interface{}类型变量其具体类型可以使用reflect.Tpye来表示,而其具体值则使用reflect.Value来表示。而reflect.Type和reflect.Value分别提供reflect.TypeOf()和reflect.ValueOf()来获取interface{}的具体类型及具体值。接下来我们结合例子来进行说明
package main
import(
"fmt"
"reflect"
)
type order struct{
ordId int
customerId int
}
func query(q interface{}) {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
fmt.Println("Type ", t)
fmt.Println("Value ", v)
}
func main(){
o := order{
ordId: 456,
customerId: 56,
}
query(o)
}
输出内容:
Type main.order
Value {456 56}
从输出的结果可以看出程序输出了interface{}的真实类型以及真实值。
4.2. reflect.Kind
在reflect还有一个比较重要的类型Kind,也是代表类型,看起来和我们前面提到的reflect.Type很相似,其实两者有着很大的差异:
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
输出结果:
Type main.order
Kind struct
通过输出结果我们能够很清楚的看出来reflect.Type和reflect.Kind:Type代表interface{}实际类型main.order;而Kind代表具体类型struct。(读起来有点绕口...)
4.3. Int() 和String()
Int()和String()主要用于从reflect.Value提取对应值作为int64和string类型
package main
import (
"fmt"
"reflect"
)
func main() {
a := 56
x := reflect.ValueOf(a).Int()
fmt.Printf("type:%T value:%v\n", x, x)
b := "Naveen"
y := reflect.ValueOf(b).String()
fmt.Printf("type:%T value:%v\n", y, y)
}
输出结果:
type:int64 value:56
type:string value:Naveen
4.4. NumField() 和Field()
NumField()方法获取一个struct所有的fields,Field(i int)获取指定第i个field的reflect.Value,结合具体实例:
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
if reflect.ValueOf(q).Kind() == reflect.Struct {
v := reflect.ValueOf(q)
fmt.Println("Number of fields", v.NumField())
for i := 0; i < v.NumField(); i++ {
fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
}
}
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
输出结果:
Number of fields 2
Field:0 type:reflect.Value value:456
Field:1 type:reflect.Value value:56
任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。与成员获取相关的 reflect.Type 的方法如下表所示。
4.4.1. 结构体成员访问的方法列表
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机 |
NumField() int | 返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机 |
FieldByNameFunc( match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机 |
4.4.2. Field实例
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func query(q interface{}) {
if reflect.ValueOf(q).Kind() == reflect.Struct {
t := reflect.TypeOf(q).Name()
sql := fmt.Sprintf("insert into %s values(", t)
v := reflect.ValueOf(q)
for i := 0; i < v.NumField(); i++ {
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
sql = fmt.Sprintf("%s%d", sql, v.Field(i).Int())
} else {
sql = fmt.Sprintf("%s, %d", sql, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
sql = fmt.Sprintf("%s\"%s\"", sql, v.Field(i).String())
} else {
sql = fmt.Sprintf("%s, \"%s\"", sql, v.Field(i).String())
}
default:
fmt.Println("Unsupported type")
return
}
}
sql = fmt.Sprintf("%s)", sql)
fmt.Println(sql)
return
}
fmt.Println("unsupported type")
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
query(o)
e := employee{
name: "Naveen",
id: 565,
address: "Coimbatore",
salary: 90000,
country: "India",
}
query(e)
i := 90 // 这是一个错误的尝试
query(i)
}
输出结果:
insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type
4.5. reflect.Elem
go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*
操作,代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 创建cat的实例
ins := &cat{}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 显示反射类型对象的名称和种类
fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
// 取类型的元素
typeOfCat = typeOfCat.Elem()
// 显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
代码输出如下:
name: '' kind: 'ptr'
element name: 'cat', element kind: 'struct'
4.6. reflect.StructField
4.6.1. StructField结构体
reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
字段说明如下
- Name:为字段名称。
- PkgPath:字段在结构体中的导入唯一标识该包的路径,例如"encoding、base64"。如果类型已经被宣告(string,error)或是未命名 (*T, struct{}, []int),路径将是空字符串。
- Type:字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息。
- Tag:结构体标签,为结构体字段标签的额外信息,可以单独提取。
- Index:FieldByIndex 中的索引顺序。
- Anonymous:表示该字段是否为匿名字段。
4.6.2. 获取成员反射信息
下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。
反射访问结构体成员类型及信息:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
// 带有结构体tag的字段
Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i)
// 输出成员名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名, 找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
代码输出如下:
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
4.7. 使用实例
//递归查找结构体中,属于infra.Plugin接口类型的所有结构体,并完成注册
func findPlugins(val reflect.Value, uniqueness map[infra.Plugin]struct{}, x ...int) (
res []infra.Plugin, err error,
) {
n := 0
if len(x) > 0 {
n = x[0]
}
var logf = func(f string, a ...interface{}) {
for i := 0; i < n; i++ {
f = "\t" + f
}
if printPluginLookupDebugs {
log.DefaultLogger.Debug(f, a...)
}
}
typ := val.Type()
logf("=> %v (%v)", typ, typ.Kind())
defer logf("== %v ", typ)
if typ.Kind() == reflect.Interface {
if val.IsNil() {
logf(" - val is nil")
return nil, nil
}
val = val.Elem()
typ = val.Type()
}
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if !val.IsValid() {
logf(" - val is invalid")
return nil, nil
}
if typ.Kind() != reflect.Struct {
logf(" - is not a struct: %v %v", typ.Kind(), val.Kind())
return nil, nil
}
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// PkgPath is empty for exported fields
if exported := field.PkgPath == ""; !exported {
continue
}
fieldVal := val.Field(i)
logf("-> field %d: %v - %v (%v)", i, field.Name, field.Type, fieldVal.Kind())
// transform field to list of values if it is slice or array
fieldVals, isList := getFieldValues(field, fieldVal)
if isList {
logf(" - found list: %v", field.Name)
}
for _, entry := range fieldVals {
var fieldPlug infra.Plugin
plug, implementsPlugin := isFieldPlugin(entry.fieldVal)
if implementsPlugin {
if plug == nil {
logf(" - found nil plugin: %v", entry.fieldName)
continue
}
_, found := uniqueness[plug]
if found {
logf(" - found duplicate plugin: %v %v", entry.fieldName, field.Type)
continue
}
uniqueness[plug] = struct{}{}
fieldPlug = plug
logf(" + FOUND PLUGIN: %v - %v (%v)", plug.String(), entry.fieldName, field.Type)
}
// do recursive inspection only for plugins and fields Deps
if fieldPlug != nil || (field.Anonymous && entry.fieldVal.Kind() == reflect.Struct) {
// try to inspect structure recursively
l, err := findPlugins(entry.fieldVal, uniqueness, n+1)
if err != nil {
logf(" - Bad field: %v %v", entry.fieldName, err)
continue
}
res = append(res, l...)
}
if fieldPlug != nil {
res = append(res, fieldPlug)
}
}
}
logf("<- got %d plugins", len(res))
return res, nil
}
type fieldValEntry struct {
fieldName string
fieldVal reflect.Value
}
func getFieldValues(field reflect.StructField, fieldVal reflect.Value) ([]*fieldValEntry, bool) {
var fieldVals []*fieldValEntry
kind := fieldVal.Kind()
if kind == reflect.Slice || kind == reflect.Array {
for i := 0; i < fieldVal.Len(); i++ {
entryName := fmt.Sprintf("%s[%d]", field.Name, i)
fieldVals = append(fieldVals, &fieldValEntry{entryName, fieldVal.Index(i)})
}
return fieldVals, true
}
// underlying list
typ := reflect.ValueOf(fieldVal.Interface()).Kind()
if typ == reflect.Slice || typ == reflect.Array {
fieldValSlice := reflect.ValueOf(fieldVal.Interface())
for i := 0; i < fieldValSlice.Len(); i++ {
entryName := fmt.Sprintf("%s[%d]", field.Name, i)
fieldVals = append(fieldVals, &fieldValEntry{entryName, fieldValSlice.Index(i)})
}
return fieldVals, true
}
// not a list (single entry)
fieldVals = append(fieldVals, &fieldValEntry{field.Name, fieldVal})
return fieldVals, false
}
var pluginType = reflect.TypeOf((*infra.Plugin)(nil)).Elem()
func logdebug(f string, a ...interface{}) {
if printPluginLookupDebugs {
log.DefaultLogger.Debug(f, a...)
}
}
func isFieldPlugin(fieldVal reflect.Value) (infra.Plugin, bool) {
logdebug(" - is field plugin:%v (%v) %v pluginType=%v", fieldVal.Type(), fieldVal.Kind(), fieldVal, pluginType)
switch fieldVal.Kind() {
case reflect.Struct:
ptrType := reflect.PtrTo(fieldVal.Type())
logdebug("ptrType %v ", ptrType)
if ptrType.Implements(pluginType) {
logdebug("implement plugin interface")
if fieldVal.CanAddr() {
if plug, ok := fieldVal.Addr().Interface().(infra.Plugin); ok {
return plug, true
}
}
return nil, true
}
logdebug("not implement plugin interface")
case reflect.Ptr, reflect.Interface:
if plug, ok := fieldVal.Interface().(infra.Plugin); ok {
if fieldVal.IsNil() {
return nil, true
}
return plug, true
}
}
return nil, false
}
5. 如何使用reflection(反射)
前面已经演示了reflection(反射)的使用,那真正的问题:该如何应用反射?
Clear is better than clever. Reflection is never clear.--- Rob Pike
reflection(反射)在golang中比较强大和高级的特性,使用时是需要注意的。因为reflection(反射)很难实现清晰并可维护的代码。遵循一条:尽量避免使用,除非方案必须使用。reflection(反射)的推导是比较复杂的,并不是为了随意使用而设计的。
- 反射的三大定律
- interface{}类型的值到反射reflecton对象.
根源上来说, reflection的原理就是检查interface中保存的一对值和类型, 所以在reflect包中,有两个类型我们需要记住, Type和Value两个类型. 通过这两个类型,我们可以访问一个interface变量的内容. 调用reflect.ValueOf和reflect.TypeOf可以检索出一个interface的值和具体类型. 当然通过reflect.Value我们也可以获得reflect.Type。 - 反射reflection对象到interface{}类型的值.
通过reflect.Value的Interface方法,我们可以获得一个Interface值。实际上这个方法将一个type和value打包回interface - 当修改一个反射reflection时, 其值必须是settable.(留个问题:如何通过reflect.Value来设置一个值,并需要注意哪些事宜)。