Go基础包系列 | reflect包相关
反射
反射是指在程序运行期对程序本身进行访问和修改的能力。
如果需要搭建功能强大的框架,那么反射是必学的,他能动态的对属性进行修改。
1 变量机制
- 变量包含类型信息和值信息
- 类型信息:reflect.TypeOf,是静态的元信息,是预先定义好的,比如名称、tag信息等
- 值信息:reflect.ValueOf,是动态的,程序运行过程中会改变
2 获取、修改信息
- 反射可以在运行时动态获取程序的各种详细信息,根据类型信息,switch分情况处理
package main
import (
"fmt"
"reflect"
)
//反射获取interface类型信息
func reflectType(a interface{}) {
t := reflect.TypeOf(a)
// kind()可以获取具体类型
k := t.Kind()
switch k {
case reflect.Float64:
fmt.Println("float64")
case reflect.String:
fmt.Println("string")
}
}
func main() {
s := "str"
reflectType(x)
}
- 反射获取interface值信息
package main
import (
"fmt"
"reflect"
)
//反射获取interface值信息
func main() {
var a float64 = 3.4
v := reflect.ValueOf(a) // 获取数值
k := v.Kind() // 获取类型
switch k { // 根据类型获取数值
case reflect.Float64:
fmt.Println("a是:", v.Float())
}
}
- 反射修改值信息
package main
import (
"fmt"
"reflect"
)
//反射修改值
func main() {
var x float64 = 0.1
v := reflect.ValueOf(&x)
// 尝试这种指针的情况
// v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Float64:
// 反射修改值,非指针报错
// v.SetFloat(2.0)
case reflect.Ptr:
// 如果是指针,则需要先调用Elem()获取地址指向的值
v.Elem().SetFloat(3.0)
}
fmt.Println("x =", x)
}
如果传的不是指针,实际上报错:
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
要修改值还是传指针比较保险!
3 结构体信息
遍历结构体信息:
- 获取数据类型、名称 -> 使用静态获取(TypeOf)
- 获取数据具体值、修改具体值 -> 使用动态获取(ValueOf)
- 使用
NumField
、NumMethod
来获取属性、方法的具体个数 Anonymous
属性来判断是否为匿名字段- 方法信息使用静态or动态都可,建议使用ValueOf,这样可以调用
// 定义结构体
type User struct {
Id int
Name string
}
// 设定方法
func (u User) Hello(prefix string) {
fmt.Println(prefix + ":" + u.Name)
}
// 传入interface{}
func rangeStruct(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println("类型:", t)
fmt.Println("字符串类型:", t.Name())
// 遍历所有属性
v := reflect.ValueOf(o)
for i := 0; i < t.NumField(); i++ {
// 取每个字段静态信息
f := t.Field(i)
// 获取字段的值信息,使用Interface(),获取字段对应的值
val := v.Field(i).Interface()
if f.Anonymous { // 如果是匿名字段,加个提示
fmt.Printf("匿名字段:")
}
fmt.Printf("%s (%v) : %v \n", f.Name, f.Type, val)
}
// 遍历所有方法,如果要调用(Call)就的使用ValueOf来获取
for i := 0; i < v.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("name: %s, 详细信息 :%v", m.Name, m.Type)
}
}
func main() {
u := User{1, "John"}
rangeStruct(u)
}
// 输出结果:
/*
类型: main.User
字符串类型: User
Id (int) : 1
Name (string) : John
name: Hello, 详细信息 :func(main.User, string)
*/
根据名称来获取、修改属性:
- 使用ValueOf来获取属性,要使用指针参数的方式修改
// 修改结构体值
func ChangeName(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() != reflect.Ptr {
fmt.Println("要使用指针形式!")
return
}
// 获取指针指向的元素
v = v.Elem()
// 取字段
f := v.FieldByName("Name")
if f.Kind() == reflect.String {
f.SetString("Tom")
}
}
func main() {
u := User{"John"}
ChangeName(&u)
fmt.Println(u)
}
4 获取字段的tag
使用TypeOf获取tag:
type Student struct {
Name string `json:"jsonName" yaml:"yamlName"`
}
func main() {
var s Student
v := reflect.ValueOf(&s)
// 类型判断,如果是指针就需要加一层Elem()
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// 获取tag字段
t := v.Type()
f := t.Field(0)
fmt.Println(f.Tag.Get("json"))
fmt.Println(f.Tag.Get("yaml"))
}
5 调用方法
使用ValueOf获取方法,再进行调用:
// 定义结构体
type User struct {
Name string
}
func (u User) Hello(prefix string) {
fmt.Println(prefix, "-", u.Name)
}
func main() {
u := User{"John"}
v := reflect.ValueOf(u)
// 获取方法
m := v.MethodByName("Hello")
if m.Kind() == reflect.Invalid {
log.Fatal("无该方法")
}
// 构建参数
args := []reflect.Value{reflect.ValueOf("hello, nice to meet you")}
// 无参数:var args []reflect.Value
// 调用方法,需要传入方法的参数
m.Call(args)
}
实现yaml解析
简化版,只有int、string的实现
yaml文件:
Username: root
Passwd: admin
Database: test
Host: 127.0.0.1
Port: 8000
核心代码:
package main
import (
"bufio"
"fmt"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"log"
"os"
"reflect"
"strconv"
"strings"
)
type Data struct {
Username string `yaml:"Username"`
Passwd string `yaml:"Passwd"`
Database string `yaml:"Database"`
Host string `yaml:"Host"`
Port int `yaml:"Port"`
}
func main() {
// 实现Data的反射填充
data := readFile()
// yaml解析
yamlData := yamlFile()
// 比较是否相同 -> 成功
fmt.Println(reflect.DeepEqual(data, yamlData))
}
// 读取文件
func readFile() Data {
data := Data{}
// 读取文件
file, err := os.Open("./conf.yaml")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// 核心逻辑
fullData(&data, line)
}
fmt.Println(data)
return data
}
// 反射填充data
func fullData(data *Data, in []byte) {
ins := string(in)
sp := strings.Split(ins, ":")
for i := range sp { // 删除前后空格
sp[i] = strings.Trim(sp[i], " ")
}
// 反射根据名称修改数值
refVal := reflect.ValueOf(data).Elem()
field := refVal.FieldByName(sp[0])
// 根据类型填入值,假设只有string、int
switch field.Kind() {
case reflect.String:
field.SetString(sp[1])
case reflect.Int:
it, err := strconv.Atoi(sp[1])
if err != nil {
log.Println("转换失败")
return
}
field.SetInt(int64(it))
}
}
// yaml解析 - 第三方解析
func yamlFile() Data {
yamlFile, err := ioutil.ReadFile("./conf.yaml")
if err != nil {
log.Fatal(err)
}
yamlData := Data{}
// 解析yaml
err = yaml.Unmarshal(yamlFile, &yamlData)
if err != nil {
log.Fatal(err)
}
fmt.Println(yamlData)
return yamlData
}