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 方法。(完)

 

posted @ 2017-11-24 14:54  有爱jj  阅读(1303)  评论(0编辑  收藏  举报