go反射实例
需求分析:
如在rocketmq的网络通信中,所有通信数据包以如下形式传输: (注:rocketmq的java结构体,这里使用了go形式表示)
type RemotingCommand struct { //header Code int `json:"code"` Language string `json:"language"` Version int `json:"version"` Opaque int32 `json:"opaque"` Flag int `json:"flag"` Remark string `json:"remark,omitempty"` ExtFields map[string]string `json:"extFields"` //body Body []byte `json:"body,omitempty"` }
其中,ExtFields 表示用户自定义数据包,如:在某次通信中传输的 ExtFields 的内容如下,接收对象为 MyResponseHeader 型。
//ExtFields 数据内容 extFields := make(map[string]string) extFields ["result"] = "true" extFields ["answer"] = "1234" //MyResopnseHeader 接口体 type MyResponseHeader struct { Result bool Answer int64 }
将 “extFields ” 转化为 MyResponseHeader 型过程中,需要将string型数据分别转换为 bool、int64等类型。
另外,不同的remotingCommand包接收到数据后需要解析成不同的结构体数据, 如何使用一个方式与统一解析数据呢? 解决这个问题需要用到反射。本文根据这个问题,对go中的反射知识进行了简单实践,具体内容如下:
涉及到的反射知识点补充
1.reflect.Value
reflect.ValueOf()的返回值类型为reflect.Value,表示值的真实内容。
var i int = 123 var s = "abc" fmt.Println(reflect.ValueOf(i)) // 123 fmt.Println(reflect.ValueOf(s)) // abc
2.reflect.Value值的设置
go中不能直接对Value进行赋值操作,如对上述变量 s 进行赋值,首先需要拿到 s 值的指针,然后拿到该指针的reflect.Value,指针的reflect.Value调用Value.Elem()后对对应到 s 值对象,继而可以对 s 进行赋值操作。
value赋值的例子:
func main(){ var i int = 123 fe := reflect.ValueOf(&i).Elem() //必须是指针的Value才能调用Elem fmt.Println(fe) // 123 fmt.Println(fe.CanSet()) // true fe.SetInt(456) fmt.Println(i) //456 }
3.reflect.Type.Kind 与 reflect.Value.Kind
它返回的是对象的基本类型,例如 Float32、Float64、int32、int64、Slice、Bool、Complex64、Array、chan、Func、Interface、Map 等等。
4.reflect.Type.Filed 与 relfect.Type.Filed
前者放回的是一个StructFiled对象。后者返回的还是一个Value对象
type StructField struct { Name string // name 常用 PkgPath string Type Type // field type 常用 Tag StructTag // field tag string Offset uintptr // offset within struct, in bytes Index []int // index sequence for Type.FieldByIndex Anonymous bool // is an embedded field }
5.reflect.Type.FiledByName 与 reflect.Value.FileByName
前者返回一个StructFiled对象,后者返回的还是一个Value对象
具体实现过程
参考rocketmq的思路,先定义一个 CustomHeader接口,自定义包实现该接口,然后定义一个解析包的方法,该方法中包括go反射的运用。
自定义数据包:
type CustomHeader interface { CheckFields() error }
结构体实现了 CustomHeader 接口:
type MyResponseHeader struct { Result bool Answer int64 } func (t *MyResponseHeader) CheckFields() error { return nil }
转化测试:
//ExtFields 数据内容 extFields := make(map[string]string) extFields ["result"] = "true" extFields ["answer"] = "1234"
err := DecodeCustomHeader(extFields, myResponseHeader) if err != nil { panic(err.Error()) } fmt.Printf("myResponseHeader.Result = %v \nmyResponseHeader.Answer = %d\n", myResponseHeader.Result, myResponseHeader.Answer)
结果:
myResponseHeader.Result = true myResponseHeader.Answer = 1234
DecodeCustomHeader代码如下:
func DecodeCustomHeader(extFields map[string]string, commandCustomHeader CustomHeader) error { structValue := reflect.ValueOf(commandCustomHeader).Elem() for k, v := range extFields { err := reflectSturctSetField(structValue, firstLetterToUpper(k), v) if err != nil { return err } } return nil } // 支持string int8 int16 int int32 int64 uint8 uint16 uint32 uint64 bool,非string类型将进行转换 func reflectSturctSetField(structValue reflect.Value, name string, value string) error { structFieldValue := structValue.FieldByName(name) if !structFieldValue.IsValid() { return errors.Errorf("No such field: %s in obj", name) } if !structFieldValue.CanSet() { return errors.Errorf("Cannot set %s field value", name) } structFieldType := structFieldValue.Type() switch structFieldType.Kind() { case reflect.String: structFieldValue.SetString(value) case reflect.Int8: fallthrough case reflect.Int16: fallthrough case reflect.Int32: fallthrough case reflect.Int64: fallthrough case reflect.Int: ival, err := strconv.ParseInt(value, 10, 64) if err != nil { return errors.Wrap(err, 0) } structFieldValue.SetInt(ival) case reflect.Uint8: fallthrough case reflect.Uint16: fallthrough case reflect.Uint32: fallthrough case reflect.Uint64: ival, err := strconv.ParseUint(value, 10, 64) if err != nil { return errors.Wrap(err, 0) } structFieldValue.SetUint(ival) case reflect.Bool: bval, err := strconv.ParseBool(value) if err != nil { return errors.Wrap(err, 0) } structFieldValue.SetBool(bval) default: return errors.Errorf("Provided value type didn't match obj field type") } return nil } // 首字母大写 func firstLetterToUpper(s string) string { if len(s) > 0 { b := []byte(s) if b[0] >= 'a' && b[0] <= 'z' { b[0] = b[0] - byte(32) s = string(b) } } return s }
只要实现了CustomHeader接口的结构体,调用DecodeCustomHeader方法,就可以获取对应的数据了。另外根据业务需要,可以扩展 reflectSturctSetField 方法。(完)