Golang实践:reflect
Go语言提供了一种机制,在编译时不知道类型的情况下, 可更新变量、在运行时查看值、调用方法以及直接对它们的布局进行操作; 这种机制称为反射;
反射也让我们可以把类型当作头等值;
为什么使用反射
有时我们需要写一个函数有能力统一处理各种值类型的函数,而这些类型可能无法共享同一个接口; 也可能布局未知,或者这个类型在我们设计函数时还不存在;
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
if arg == nil {
switch verb {
case 'T', 'v':
p.fmt.padString(nilAngleString)
default:
p.badVerb(verb)
}
return
}
// Special processing considerations.
// %T (the value's type) and %p (its address) are special; we always do them first.
switch verb {
case 'T':
p.fmt.fmtS(reflect.TypeOf(arg).String())
return
case 'p':
p.fmtPointer(reflect.ValueOf(arg), 'p')
return
}
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex(f, 128, verb)
case int:
p.fmtInteger(uint64(f), signed, verb)
case int8:
p.fmtInteger(uint64(f), signed, verb)
case int16:
p.fmtInteger(uint64(f), signed, verb)
case int32:
p.fmtInteger(uint64(f), signed, verb)
case int64:
p.fmtInteger(uint64(f), signed, verb)
case uint:
p.fmtInteger(uint64(f), unsigned, verb)
case uint8:
p.fmtInteger(uint64(f), unsigned, verb)
case uint16:
p.fmtInteger(uint64(f), unsigned, verb)
case uint32:
p.fmtInteger(uint64(f), unsigned, verb)
case uint64:
p.fmtInteger(f, unsigned, verb)
case uintptr:
p.fmtInteger(uint64(f), unsigned, verb)
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
/usr/local/go/src/fmt/print.go
reflect.Type和reflect.Value
reflect.TypeOf示例:
func GetType() {
t := reflect.TypeOf(3)
fmt.Println(t.String())
fmt.Println(t)
// === RUN TestGetType
// int
// int
}
reflect.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 interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
reflect.ValueOf示例:
func GetValue() {
t := reflect.ValueOf(3)
fmt.Println(t)
fmt.Println(t.String())
}
=== RUN TestGetValue
3
<int Value>
reflect.ValueOf源码:
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
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).
escapes(i)
return unpackEface(i)
}
使用reflect.Value来设置值
func SetValue() {
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()
fmt.Println(a, a.CanAddr())
fmt.Println(b, b.CanAddr())
fmt.Println(c, c.CanAddr())
fmt.Println(d, d.CanAddr())
d.Set(reflect.ValueOf(3))
fmt.Println(x)
d.SetInt(9)
fmt.Println(x)
}
=== RUN TestSetValue
2 false
2 false
0xc000016120 false
2 true
3
9
访问结构体字段标签
valueInf := reflect.ValueOf(x)
typeInf := reflect.TypeOf(x)
if reflect.TypeOf(x).Kind() == reflect.Ptr {
valueInf = reflect.ValueOf(x).Elem()
typeInf = reflect.TypeOf(x).Elem()
}
// fmt.Println("num field of interface type: ", typeInf.NumField())
for i := 0; i < typeInf.NumField(); i++ {
fieldOfType := typeInf.Field(i)
valueOfTypeInField := valueInf.FieldByName(fieldOfType.Name)
if enum, ok := fieldOfType.Tag.Lookup("enum"); ok {
if typeKind == reflect.String && valueOfTypeInField.String() != "" {
enums := strings.Split(enum, ",")
for _, value := range enums {
if valueOfTypeInField.String() == value {
return nil
}
}
return NewError(EnumsNotSupport, fmt.Sprintf("Request body invalid: unsupport value (%s) of (%s)", valueOfTypeInField, fieldOfType.Name))
}
}
}
return
}
显示类型的方法
func GetMethod(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type: %v\n", t)
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("func: %s %s %s\n", t, t.Method(i).Name, methodType.String())
}
}
// type: time.Duration
// func: time.Duration Hours func() float64
// func: time.Duration Microseconds func() int64
// func: time.Duration Milliseconds func() int64
// func: time.Duration Minutes func() float64
// func: time.Duration Nanoseconds func() int64
// func: time.Duration Round func(time.Duration) time.Duration
// func: time.Duration Seconds func() float64
// func: time.Duration String func() string
// func: time.Duration Truncate func(time.Duration) time.Duration
示例:参数检验
// ValidateParams base check parameters in request body
func ValidateParams(modelPointer interface{}) (err RespError) {
glog.Infof("Validate request body parameters : , type is %s. \n", reflect.TypeOf(modelPointer))
defer func() {
errRe := recover()
if errRe != nil {
glog.Errorf("request body validate error: %+v, detail: %+v.", errRe, modelPointer)
err = NewError(RequestBodyInvalid, fmt.Sprintf("request body invalid: %+v", errRe))
}
}()
valueInf := reflect.ValueOf(modelPointer)
typeInf := reflect.TypeOf(modelPointer)
if reflect.TypeOf(modelPointer).Kind() == reflect.Ptr {
valueInf = reflect.ValueOf(modelPointer).Elem()
typeInf = reflect.TypeOf(modelPointer).Elem()
}
// fmt.Println("num field of interface type: ", typeInf.NumField())
for i := 0; i < typeInf.NumField(); i++ {
fieldOfType := typeInf.Field(i)
valueOfTypeInField := valueInf.FieldByName(fieldOfType.Name)
typeKind := fieldOfType.Type.Kind()
if typeKind == reflect.Struct {
err := ValidateParams(valueOfTypeInField.Interface())
if err != nil {
return err
}
} else if typeKind == reflect.String {
err := ValidateBaseParams(fieldOfType, valueOfTypeInField)
if err != nil {
return err
}
}
if enum, ok := fieldOfType.Tag.Lookup("enum"); ok {
if typeKind == reflect.String && valueOfTypeInField.String() != "" {
enums := strings.Split(enum, ",")
for _, value := range enums {
if valueOfTypeInField.String() == value {
return nil
}
}
return NewError(EnumsNotSupport, fmt.Sprintf("Request body invalid: unsupport value (%s) of (%s)", valueOfTypeInField, fieldOfType.Name))
}
}
}
return
}
注意事项
反射是一个功能和表达能力都很强大的工具,但应该谨慎使用它;
1.基于反射的代码是很脆弱的;
能导致编译器报类型错误的每种写法,在反射中都有一个对应的误用方法;
编译器在编译时会报告这个错误,而反射错误则要等到执行时才以崩溃的方式来报告;
而这可能是代码开始执行很久后才会发生的事;
确保反射的使用完整地封装在包里面,并且如果可能,在包的API中避免使用reflect.Value; 尽量使用特定的类型来确保输入是合法的值;
2.类型其实也算是某种形式的文档,而反射的相关操作则无法做静态类型检查; 所以大量使用反射的代码是很难理解的;
3.基于反射的函数会比为特定类型优化的函数慢一两个数量级;
go版本
go version go1.13.6 darwin/amd64
refs
《The Go Programming Language》Alan A.A, Donovan & Brian W.Kernighan
http://c.biancheng.net/golang/reflect/