反射

反射是指在程序运行期对程序本身进行访问和修改的能力。

如果需要搭建功能强大的框架,那么反射是必学的,他能动态的对属性进行修改。


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 结构体信息

遍历结构体信息:

  1. 获取数据类型、名称 -> 使用静态获取(TypeOf)
  2. 获取数据具体值、修改具体值 -> 使用动态获取(ValueOf)
  3. 使用NumFieldNumMethod来获取属性、方法的具体个数
  4. Anonymous属性来判断是否为匿名字段
  5. 方法信息使用静态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)
*/

根据名称来获取、修改属性:

  1. 使用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
}