目录
反射机制
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行文件。因为可执行文件不存在变量名,所以在程序运行时,就无法通过变量名作为句柄并获取到自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如:结构体信息、字段名称、类型信息等整合到可执行文件中,并给程序提供接口访问这些反射信息,这样就可以在程序运行期间通过接口来获取到变量的反射信息,并且有能力修改它们。
reflect 包
Struct Tags 与 Reflect
举一个例子:Golang 中的变量名是大小写敏感的,对于小写的局部变量,在包外无法被引用。但实际上,Golang 程序是经常需要与其它系统进行数据交互的,例如:Redis 数据库访问。这样就需要一种设计,可以灵活的将 Golang 最核心的数据类型 Struct 转换为其他数据格式,这就是 Struct Tags。在需要将 Struct 转换为其它数据格式时,会根据 Tags 中定义的信息进行转换。
Struct Tags 类似注释,使用反引号 “`”,Golang 提供了 reflect 包来对 Struct Tags 进行解析。
示例:
package main
import (
"fmt"
"reflect"
)
type employee struct {
ID int `json:"id"`
Name string `json:"名字" validate:"presence,min=2,max=40"`
Age int `json:"年龄"`
Desc string `json:"描述" back:"好看否"`
weight float64 `json:"weight" 单位:"kg"`
Salary float64 `json:"-"`
Email string `validate:"email,required"`
MateName string `json:"mate_name,omitempty"`
}
func main() {
zhangsan := employee{
ID: 1,
Name: "张三",
Age: 18,
Desc: "秀色可餐",
weight: 48.0,
Salary: 12.0,
MateName: "Prince",
}
t := reflect.TypeOf(zhangsan)
fmt.Println("Type: ", t.Name())
fmt.Println("Kind: ", t.Kind())
fmt.Println("Num: ", t.NumField())
tagName := "validate"
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get(tagName)
fmt.Printf("%d. %v(%v), tag:'%v'\n", i+1, field.Name, field.Type.Name(), tag)
}
}
结果:
Type: employee
Kind: struct
Num: 8
1. ID(int), tag:''
2. Name(string), tag:'presence,min=2,max=40'
3. Age(int), tag:''
4. Desc(string), tag:''
5. weight(float64), tag:''
6. Salary(float64), tag:''
7. Email(string), tag:'email,required'
8. MateName(string), tag:''
通过 reflect 包,程序能够获取结构体的基本信息,包括它的成员清单、成员名称和成员类型。调用 field.Tag.Get 方法可以返回与 tagName 名匹配的 Struct Tags 的字符串,基于此我们就可以自由地去实现想要的逻辑了。
例如 json.Marshal 方法就有使用到反射机制,来完成 Struct 和 JSON 之间的数据格式转换。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type employee struct {
ID int `json:"id"`
Name string `json:"名字" validate:"presence,min=2,max=40"`
Age int `json:"年龄"`
Desc string `json:"描述" back:"好看否"`
weight float64 `json:"weight" 单位:"kg"`
Salary float64 `json:"-"`
Email string `validate:"email,required"`
MateName string `json:"mate_name,omitempty"`
}
func main() {
zhangsan := employee{
ID: 1,
Name: "张三",
Age: 18,
Desc: "秀色可餐",
weight: 48.0,
Salary: 12.0,
MateName: "Prince",
}
fmt.Println(zhangsan)
re, _ := json.Marshal(zhangsan)
fmt.Println(string(re))
}
结果:
$ go run tst.go
{1 张三 18 秀色可餐 48 12 Prince}
{"id":1,"名字":"张三","年龄":18,"描述":"秀色可餐","Email":"","mate_name":"Prince"}
其中,有以下要点需要注意:
- 如果结构体成员名称首字母为小写时,则不进行转换。例如:weight。
- 如果 Tag 中定义了
json:"XXX",则 XXX 作为 JSON key。 - 如果没有使用 Tag
json:"XXX",则 JSON key 和 Struct 成员名保持一致。 - 如果 Tag 中定义了
json:"-",则不进行转换。 - 如果 Tag 中定义了
json:",omitempty",则表示当成员数值为空时,可直接忽略。
可见,在 json 包中,Struct Tag json:"XXX" 是用来指导 json.Marshal/Unmarshal 的。
此外,我们还可以通过 Struct Tags 中的其他 Tag 来实现别的功能,例如:限定成员 Age 的值在 1-100 之间。
$ cat main.go
package main
import (
"fmt"
"strings"
"strconv"
"reflect"
_ "encoding/json"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age" valid:"1-100"`
}
func (p * Person) validation() bool {
v := reflect.ValueOf(*p)
tag := v.Type().Field(1).Tag.Get("valid")
val := v.Field(1).Interface().(int)
fmt.Printf("tag=%v, val=%v\n", tag, val)
result := strings.Split(tag, "-")
var min, max int
min, _ = strconv.Atoi(result[0])
max, _ = strconv.Atoi(result[1])
if val >= min && val <= max {
return true
} else {
return false
}
}
func main() {
person1 := Person { "tom", 12 }
if person1.validation() {
fmt.Printf("person 1: valid\n")
} else {
fmt.Printf("person 1: invalid\n")
}
person2 := Person { "tom", 250 }
if person2.validation() {
fmt.Printf("person 2 valid\n")
} else {
fmt.Printf("person 2 invalid\n")
}
}
这么例子我们给Person添加了一个validate函数,validate验证age是不是合理。这个函数可以扩展对任意struct的任意valid域进行验证。
$ cat main.go
package main
import (
"fmt"
"strings"
"strconv"
"reflect"
_ "encoding/json"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age" valid:"1-100"`
}
type OtherStruct struct {
Age int `valid:"20-300"`
}
func validateStruct(s interface{}) bool {
v := reflect.ValueOf(s)
for i := 0; i < v.NumField(); i++ {
fieldTag := v.Type().Field(i).Tag.Get("valid")
fieldName := v.Type().Field(i).Name
fieldType := v.Field(i).Type()
fieldValue := v.Field(i).Interface()
if fieldTag == "" || fieldTag == "-" {
continue
}
if fieldName == "Age" && fieldType.String() == "int" {
val := fieldValue.(int)
tmp := strings.Split(fieldTag, "-")
var min, max int
min, _ = strconv.Atoi(tmp[0])
max, _ = strconv.Atoi(tmp[1])
if val >= min && val <= max {
return true
} else {
return false
}
}
}
return true
}
func main() {
person1 := Person { "tom", 12 }
if validateStruct(person1) {
fmt.Printf("person 1: valid\n")
} else {
fmt.Printf("person 1: invalid\n")
}
person2 := Person { "jerry", 250 }
if validateStruct(person2) {
fmt.Printf("person 2: valid\n")
} else {
fmt.Printf("person 2: invalid\n")
}
other1 := OtherStruct { 12 }
if validateStruct(other1) {
fmt.Printf("other 1: valid\n")
} else {
fmt.Printf("other 1: invalid\n")
}
other2 := OtherStruct { 250 }
if validateStruct(other2) {
fmt.Printf("other 2: valid\n")
} else {
fmt.Printf("other 2: invalid\n")
}
}
在这个例子中我们定义了一个函数validateStruct,接受任意一个struct作为参数;validateStruct为验证struct中所有定义的Age字段,如果字段名字是Age,字段类型是int,并且定义了valid tag,那么就会验证这个valid是否有效。
来源:oschina
链接:https://my.oschina.net/u/4353795/blog/4496510