golang 反射的基本使用、通过反射获取结构体标签案例
一、反射简介
首先大概介绍一下反射,在Go中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value,
任何接口值都可以使用reflect包中的reflect.TypeOf 和 reflect.ValueOf两个函数去获取其对应的reflect.Type和reflect.Value
二、reflect.Type
对于任意接口值,我们可以通过调用reflect.TypeOf函数获取reflect.Type类型的对象,从而获取动态类型信息。
值得注意的是如果传入的变量是一个机构体,那么reflect.Type对象可以调用Field等方法来获取reflect.StructField结构体。
这个结构体内包含了结构体变量字段的一些例如字段的类型、字段的标签、等信息,其中PkgPath字段是非导出字段的包路径,对导出字段该字段为空,由此可以判断结构体字段的导出性
简单示例如下:
package main
import (
"fmt"
"reflect"
"testing"
)
func TestType(t *testing.T) {
a := struct {
Say string
weight int
}{
Say: "Cello golang",
weight : 111,
}
typeOfA := reflect.TypeOf(a)
fmt.Printf("a 的类型是%s \n", typeOfA.Kind())
for i := 0; i < typeOfA.NumField(); i++ {
structField := typeOfA.Field(i)
if structField.PkgPath == "" {
fmt.Printf("字段%s可导出",structField.Name)
}else {
fmt.Printf("字段%s不可导出",structField.Name)
}
fmt.Printf("类型是%s\n",structField.Type)
}
}
reflect.Value
reflect.ValueOf方法可以得到变量的值信息,利用reflect.ValueOf返回的是一个reflect.Value结构体类型,
reflect.Value提供给我们一些方法可以获取变量保存的值,同时也提供了诸如SetFloat、SetString等方法去设置变量的值
要注意的是想要设置变量的值的时候,传入reflect.ValueOf的必须是个指针,因为值类型的传递是拷贝了一个副本,并不能改变原始变量的值
并且要通过reflect.Elem()方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作。
代码如下:
package main
import (
"reflect"
"testing"
)
func TestValue(t *testing.T) {
var a string = "Hello golang"
v := reflect.ValueOf(&a)
t.Logf("v 映射的类型是 %v\n", v.Type())
v.Elem().SetString("Hi")
t.Logf("现在a 的值是 %v", a)
}
通过反射实现结构体字段校验
下面分享一个通过结构体标签,进行结构体字段的长度判断、前后去空格的方法:
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
"testing"
)
//测试用例
func TestLength(t *testing.T) {
//tag-length 用于最大长度判断
//tag-must 用于空值判断
var testDatas = []struct{
Name string `length:"10" must:"Y"`
Age string `length:"2" must:"Y" `
want string
}{
{
Name: " 123456789 ",
Age: " 22222 ",
want: "Age字段长度不能大于2个字符",
},
{
Name: "",
Age: "10",
want: "Name字段不能为空",
},
{
Name: "小张",
Age: "18",
want: "",
},
}
for _ ,tt := range testDatas{
verifyResult := VerifyStructField(&tt)
if verifyResult != tt.want {
t.Errorf( "verifyResult is %s but want %s",verifyResult,tt.want)
}
}
}
//验证结构体字段
func VerifyStructField(inStruct interface{}) (verifyResult string) {
rType := reflect.TypeOf(inStruct)
rVal := reflect.ValueOf(inStruct)
if rType.Kind() == reflect.Ptr {
rType = rType.Elem()
rVal = rVal.Elem()
} else {
return fmt.Sprintf("必须传入指针")
}
for i := 0; i < rType.NumField(); i++ {
tFiled := rType.Field(i)
vFiled := rVal.Field(i)
must := tFiled.Tag.Get("must")
tagLength := tFiled.Tag.Get("length")
if tFiled.Type.Kind() == reflect.String {
v := vFiled.String()
v = strings.Trim(v, " ")
if must == "Y" && len(v) == 0 {
return fmt.Sprintf("%s字段不能为空", tFiled.Name)
}
if tagLength != "" {
filedLen := len(v)
length, _ := strconv.Atoi(tagLength)
if filedLen > length {
return fmt.Sprintf("%s字段长度不能大于%s个字符", tFiled.Name, tagLength)
}
}
if tFiled.PkgPath == "" {
vFiled.Set(reflect.ValueOf(v))
}
}
}
return
}