Golang 反射简单应用--参数校验
以下内容为个人学习总结,如果有不准确的地方,欢迎指出!
说实话我之前用Python基本没怎么用过反射,估计在Golang里面也一样,在大多数应用和服务中并不常见。
提到反射,就必须要提一下Golang反射的三大定律
- 1 可以将
interface{}
类型转换为reflect
类型。 - 2 通过反射对象可以获取
interface{}
变量。 - 3 值是否可以被更改,能被寻址。(概念不好理解,后面demo解释)
原文
- 1 Reflection goes from interface value to reflection object.
- 2 Reflection goes from reflection object to interface value.
- 3 To modify a reflection object, the value must be settable.
三大定律原出处:
https://blog.golang.org/laws-of-reflection
反射的一般使用场景
- 不确定预定类型的参数,需要根据参数的类型来执行不同的操作。
当然也可以使用
Assertion
来判断类型,但是这种方式非常麻烦。
// 通过断言判断类型
func AssertionType(v interface{}){
switch v.(type){
case int, int16, int32, int64:
fmt.Printf("整数类型 %d \n", v)
case userInfo:
fmt.Printf("是 userInfo 类型 %v ", v)
fmt.Printf("年龄 %d \n", v.(userInfo).Age)
// 断言调用方法
v.(userInfo).SayAge()
default:
fmt.Println("没有匹配到")
}
}
"但是我们如何处理其它类似[]float64、map[string][]string等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。还有如何处理类似url.Values这样的具名类型呢?即使类型分支可以识别出底层的基础类型是map[string][]string,但是它并不匹配url.Values类型,因为它们是两种不同的类型,而且switch类型分支也不可能包含每个类似url.Values的类型,这会导致对这些库的依赖。"--出自Go语言程序设计
没有办法来检查未知类型的表示方式,我们被卡住了。这就是我们为何需要反射的原因。
Golang中反射常用的方法
reflect.TypeOf
获取类型 以及相关信息(对应第一条定律)reflect.ValueOf
获取数据的运行时的表示(对应第二条定律)
其他基础方法(部分)
- 结构体反射常用 假设
user
为实例化结构体- reflect.TypeOf(user).Field(int) // 获取 指定结构体下角标属性的类型
- reflect.TypeOf(user).Field(int).Tag.Get("foo") // 获取指定下角标属性的结构体标签 内部再调用 Lookup方法
- reflect.ValueOf(user).NumField() // 获取结构体属性数量
- reflect.ValueOf(user).Kind() // 获取对应的类型
- reflect.ValueOf(user).MethodByName("SayName").Call([]reflect.Value{reflect.ValueOf("测试")}) // 传参数调用方法
- map slice获取获取 (n为map或者slice)
- reflect.ValueOf(n).Len() // 获取长度
一些关于反射的演示
// 创建一个结构体
type userInfo struct {
User string `json:"user"foo:"test_tag"`
Avatar string `json:"avatar"bar:""`
Age int `json:"age"`
}
// 定义两个方法
func (u userInfo) SayName(name string){
fmt.Printf("用户名是 %s\n", name)
}
// 方法
func (u userInfo) SayAge(){
fmt.Printf("年龄是 %d\n", u.Age)
}
func TestReflect(t *testing.T) {
// 实例化结构体
user := userInfo{User: "Nike", Age:18}
// 断言判断类型 功能不如反射强大
AssertionType(user)
AssertionType(1111)
typ := reflect.TypeOf(user) // 获取reflect的类型
t.Log(typ)
val := reflect.ValueOf(user) // 获取reflect的值
t.Log(val)
kd := val.Kind() // 获取到st对应的类别
t.Log(kd)
num := val.NumField() // 获取值字段的数量
t.Log(num)
// 通过反射调用方法
m1 := val.MethodByName("SayName")
//m1 := val.Method(0)
m1.Call([]reflect.Value{reflect.ValueOf("测试")}) // 传参数
// 私有方法不可反射调用 Java反射可以暴力调用私有方法
m2 := val.MethodByName("SayAge")
m2.Call([]reflect.Value{}) // 不传参数
tagVal := typ.Field(2) // 获取index为2的类型信息
val = val.Field(2) // 获取index为2实例化后的值
t.Log(tagVal)
t.Log(val)
t.Log(tagVal.Type) // 输出结构体字段的类型
t.Log(tagVal.Name) // 输出结构体字段名称
// 获取结构体的tag 没有则为空 Get 实际就是调用的Lookup
t.Log(tagVal.Tag.Get("foo"))
// 返回两个值 第一个为tag值 第二个为bool值 true表示设置了此tag 无论是否为空字符串
t.Log(tagVal.Tag.Lookup("foo"))
t.Log(typ.Field(1).Tag.Lookup("bar")) // 设置了tag为bar 但是为空字符串 依旧为true
t.Log(typ.Field(1).Tag.Lookup("any_tag")) // 没有设置此tag 就为false
// 必须使用地址 才可以修改原来的值 否则会panic (反射第三定律,值可以被修改)
modifyVal(&user)
t.Log(user)
}
// 通过反射修改值
func modifyVal(user interface{}){
// 获取变量的指针
pVal := reflect.ValueOf(user) // 获取reflect的值
// 获取指针指向的变量
v := pVal.Elem()
// 找到并更新变量的值
v.FieldByName("User").SetString("Jack")
}
反射的简单应用
最简单的例子,通过反射对比slice
或者map
是否相等
规定Golang中slice,map,func不能用 == 比较
https://stackoverflow.com/questions/37900696/why-cant-go-slice-be-used-as-keys-in-go-maps-pretty-much-the-same-way-arrays-ca
https://golang.org/ref/spec#Comparison_operators
sliceA := []int{1, 2, 3}
sliceB := []int{1, 2, 3}
//fmt.Println(sliceA == sliceB) // panic
// 可以用反射对比
fmt.Println(reflect.DeepEqual(sliceA, sliceB))
利用反射设计一个校验参数的方法
主要参考 gin-vue-admin 参数验证 我自己添加了一个正则校验的方法。
比如定义一个verify
来验证结构体里面的方法是否合法
// 存放验证规则的地方 一个字段可以有多个校验方法
type Rules map[string][]string
var (
UserInfoVerify = Rules{"Page": {Ge("1")}, "PageSize": {Le("50")}, "Name": {Regex(`^\d{3}$`), NotEmpty()}}
)
type UserInfo struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Name string `json:"name"`
}
func TestVerify(t *testing.T) {
// 参数信息 可以和请求的参数信息绑定
u := UserInfo{Page: 1, PageSize: 30, Name: "234"} // 合法
//u := UserInfo{Page:1, PageSize:30, Name: "1234"} // Name非法
// 验证参数是否合法 verify 自定义的校验蚕农书
if err := verify(u, UserInfoVerify); err != nil {
t.Log(fmt.Sprintf("验证失败 %s \n", err))
} else {
t.Log("success")
}
}
// 核心函数
func verify(st interface{}, roleMap Rules) (err error) {
// 限定 比较返回值为 以下几个
compareMap := map[string]bool{
"lt": true,
"le": true,
"eq": true,
"ne": true,
"ge": true,
"gt": true,
}
typ := reflect.TypeOf(st)
val := reflect.ValueOf(st)
// 判断待验证参数 是否是结构体 不是直接返回错误
if val.Kind() != reflect.Struct {
return errors.New("expect struct")
}
// 遍历结构体的所有字段
for i := 0; i < val.NumField(); i++ {
// 获取反射后的具体字段
tagVal := typ.Field(i)
val := val.Field(i)
// 判断此字段是否有校验规则 >0 则说明有
if len(roleMap[tagVal.Name]) > 0 {
// 循环此字段的校验规则(一个字段可以存在多个校验规则) 规则为 各个判断类型函数 返回值
for _, v := range roleMap[tagVal.Name] {
switch {
// 非空判断
case v == "notEmpty":
if isBlank(val) {
return errors.New(tagVal.Name + "值不能为空")
}
// 正则校验
case strings.Split(v, "=")[0] == "regex":
if !isRegexMatch(val.String(), v) {
return errors.New(tagVal.Name + "正则校验不合法" + v)
}
// 比较符判断 分割返回值里面的 = 符号 compareMap 确保输入的函数正确
case compareMap[strings.Split(v, "=")[0]]:
// 比较值 val 为反射字段后的值 v为 lt=1等校验值
if !compareVerify(val, v) {
return errors.New(tagVal.Name + "长度或值不在合法范围," + v)
}
default:
fmt.Println("检查 Rules 校验函数输入是否正确: " + v)
}
}
}
}
return nil
}
以上代码不完整,完整代码见GitHub地址
反射应用参数校验代码地址
原文地址见个人博客