go反射使用及proto协议字段随机动态赋值
1. 基本概念
- Go 语言的反射是一种在运行时动态访问程序元数据的能力。反射可以让我们在运行时检查类型和变量,例如它的大小、方法和动态的值等。这种机制让我们可以编写更加通用的函数和类型,而不需要关心具体的类型。
- 在 Go 语言中,反射的实现主要依赖于两种类型:
Type
和Value
。这两种类型都定义在reflect
包中。Type
:代表 Go 语言中的类型。通过Type
,我们可以获取类型的名称、种类、包路径、大小、是否为指针类型、是否为接口类型、是否可导出等信息。我们可以通过reflect.TypeOf
函数获取一个值的Type
。Value
:代表 Go 语言中的值。通过Value
,我们可以获取值的类型、种类、是否可导出、是否可寻址、是否可修改等信息。我们还可以通过Value
修改值、调用方法等。我们可以通过reflect.ValueOf
函数获取一个值的Value
。
- 在 Go 语言的反射机制中,还有一个重要的概念是种类(Kind)。每个
Type
都有一个对应的种类,例如int
、float64
、struct
、pointer
等。我们可以通过Type.Kind
方法获取一个类型的种类。 - Go 语言的反射机制虽然强大,但也有一些限制。例如,我们不能通过反射创建一个接口值的具体值,也不能获取一个非导出字段的
Value
。此外,使用反射会有一定的性能开销,因此在编写性能敏感的代码时,应尽量避免使用反射。 - 总的来说,Go 语言的反射是通过
Type
和Value
这两种类型,以及种类(Kind)这个概念来实现的。通过反射,我们可以在运行时动态地访问和修改程序的元数据。
2. 接口
a) reflect.TypeOf(xxx)
b) reflect.ValueOf(xxx)
c) reflect.New(xxx)
返回的都是指针,如果需要获取对象需要通过 ret.Elem() 获取
3. 用例
//随机给proto协议对象赋值
func randMsg() (int32, proto.Message) {
var msgId int32
if common.Random(0, 4) == 0 {
//25%的概率发送ack or ntf协议
var msgIds []int32
for id, name := range pb_server.MessageID_name {
if strings.Contains(name, "ACK") || strings.Contains(name, "NTF") {
msgIds = append(msgIds, id)
}
}
msgId = msgIds[common.Random(0, len(msgIds))]
} else {
//75%的概率发送req协议
var msgIds []int32
for id, name := range pb_server.MessageID_name {
if strings.Contains(name, "REQ") {
msgIds = append(msgIds, id)
}
}
msgId = msgIds[common.Random(0, len(msgIds))]
}
//msgId = 1064 //slice test
//msgId = 1111 //map test
proname := "server." + common.ProtoMsgTrans(pb_server.MessageID_name[msgId])
obj, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(proname))
if err != nil {
l4g.Debug("RandMsgEvent name:%s err:%v", proname, err)
return 0, nil
}
msg := proto.MessageV1(obj.New())
l4g.Debug("RandMsgEvent name:%s", proname)
// 获取 Message 的反射类型和值
msgType := reflect.TypeOf(msg).Elem()
msgValue := reflect.ValueOf(msg).Elem()
foreachField(msgType, &msgValue)
return msgId, msg
}
func foreachField(tp reflect.Type, value *reflect.Value) {
for i := 0; i < tp.NumField(); i++ {
field := tp.Field(i)
if strings.HasPrefix(field.Name, "XXX_") {
continue
}
//l4g.Debug("forechField i:%d name:%s type:%v", i, field.Name, field.Type.Kind().String())
fieldValue := value.Field(i)
setValue(&field, &fieldValue)
}
}
func setValue(field *reflect.StructField, value *reflect.Value) {
switch field.Type.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
{
var tmp int64
switch field.Type.Kind() {
case reflect.Int:
tmp = int64(math.MaxInt)
case reflect.Int8:
tmp = int64(math.MaxInt8)
case reflect.Int16:
tmp = int64(math.MaxInt16)
case reflect.Int32:
tmp = int64(math.MaxInt32)
case reflect.Int64:
tmp = int64(math.MaxInt64)
}
rand := common.Random(0, 3)
if rand == 0 {
value.SetInt(int64(0))
} else if rand == 1 {
value.SetInt(int64(common.Random(0, int(tmp))))
} else {
value.SetInt(tmp)
}
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
{
var tmp uint64
switch field.Type.Kind() {
case reflect.Uint:
tmp = uint64(math.MaxUint)
case reflect.Uint8:
tmp = uint64(math.MaxUint8)
case reflect.Uint16:
tmp = uint64(math.MaxUint16)
case reflect.Uint32:
tmp = uint64(math.MaxUint32)
case reflect.Uint64:
tmp = uint64(math.MaxUint64)
}
rand := common.Random(0, 3)
if rand == 0 {
value.SetUint(uint64(0))
} else if rand == 1 {
value.SetUint(uint64(common.Random(0, int(tmp)))) //tmp越界忽略
} else {
value.SetUint(tmp)
}
}
case reflect.String:
{
value.SetString("hello world!!!")
}
case reflect.Float32, reflect.Float64:
{
value.SetFloat(float64(common.Random(0, 100)))
}
case reflect.Bool:
{
value.SetBool(common.Random(0, 2) == 0)
}
case reflect.Ptr:
{
newType := field.Type.Elem()
newVal := reflect.New(newType).Elem()
foreachField(newType, &newVal)
value.Set(newVal.Addr())
}
case reflect.Struct:
//proto生成的代码结构都是指针,无需实现
case reflect.Slice:
{
sliceType := field.Type
sliceLen := common.Random(1, 6) // 随机生成长度为 1 到 5 的切片
newSlice := reflect.MakeSlice(sliceType, sliceLen, sliceLen)
elemType := sliceType.Elem()
for i := 0; i < sliceLen; i++ {
elem := newSlice.Index(i)
if elem.Type().Kind() == reflect.Ptr {
newElem := reflect.New(elem.Type().Elem()).Elem()
foreachField(elemType.Elem(), &newElem)
elem.Set(newElem.Addr())
} else {
elemField := &reflect.StructField{
Name: field.Name,
PkgPath: field.PkgPath,
Type: elemType,
Tag: field.Tag,
Offset: field.Offset,
Index: field.Index,
Anonymous: field.Anonymous,
}
setValue(elemField, &elem)
}
newSlice.Index(i).Set(elem)
}
value.Set(newSlice)
}
case reflect.Map:
{
mapType := field.Type
mapLen := common.Random(1, 6) // 随机生成长度为 1 到 5 的映射
newMap := reflect.MakeMapWithSize(mapType, mapLen)
keyType := mapType.Key()
elemType := mapType.Elem()
fmt.Println("map type:", mapType.Kind(), "elemType:", elemType.Kind())
for i := 0; i < mapLen; i++ {
//key
newKey := reflect.New(keyType).Elem()
keyField := reflect.StructField{
Name: field.Name + "_key",
PkgPath: field.PkgPath,
Type: keyType,
Tag: field.Tag,
Offset: field.Offset,
Index: field.Index,
Anonymous: field.Anonymous,
}
setValue(&keyField, &newKey)
//value
newElem := reflect.New(elemType).Elem()
//fmt.Println("newElem kind:", newElem.Kind(), "typename:", newElem.Type().Name())
if elemType.Kind() == reflect.Ptr {
tmpElem := reflect.New(newElem.Type().Elem()).Elem()
foreachField(elemType.Elem(), &tmpElem)
newElem.Set(tmpElem.Addr())
} else if elemType.Kind() == reflect.Struct {
foreachField(elemType, &newElem)
} else {
elemField := reflect.StructField{
Name: field.Name + "_value",
PkgPath: field.PkgPath,
Type: elemType,
Tag: field.Tag,
Offset: field.Offset,
Index: field.Index,
Anonymous: field.Anonymous,
}
setValue(&elemField, &newElem)
}
newMap.SetMapIndex(newKey, newElem)
}
value.Set(newMap)
}
default:
{
l4g.Error("setFieldValue unkown kind:%v name:%s", field.Type.Kind(), field.Name)
}
}
}
4. 总结:
指针与对象转换 Kind(),Elem(),Addr(),CanSet(),Name()接口,要求区分返回的到底是指针还是对象实例