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
}