转载

Go reflect 反射- Type & Value & Field & Method

其实 TypeValue 本质就是对于Golang底层数据的一个封装罢了,其实就是基于iface和eface进行可以编程级别的开发,因为那俩对象对于开发者来说属于黑盒子。 为什么我多拿出 FieldMethod 本质上为了体现这俩的重要性,也会着重讲到。

反射的几个重点: 1、安全的使用反射 2、依靠 Type 可以生成对于类型的数据,核心在于 NewSet 方法 3、理解 TypeValue 的用法 4、理解api的使用,熟练掌握 5、学会使用反射进行 IOC 操作

Type

​ Type 也就是元信息,类似于Java的Class对象,拿到Type基本可以做所有的一切,包含结构信息,字段信息,方法信息等等,所以这个是重点,Java有的功能Go基本都有,唯一区别的是字节码动态修改注入,编译性语言必然缺失的一部分,因为所谓的类型都是程序启动前都确定好的,不可修改的,这部分内容是最重要的。

​ 还要需要补充的,使用reflect包,需要核心关注边界的点,必须注意,也是最为核心关注的,因为有些方法调用是有条件的。

​ reflect.Typeof() 在获取的时候可以传入一个空指针,只要这个空指针是有类型的就可以!

结构体

​ 核心关注与边界,官方的注释很详细,如果想要使用哪个方法需要看清楚使用边界

type Type interface {
	// Methods applicable to all types.,对应着unsafe的align,是一个计算大小的方法
	Align() int
	// 字段大小,必须是type.kind=结构体类型
	FieldAlign() int
	// It panics if i is not in the range [0, NumMethod()).
  // 很重要,区别于Value.Method()方法,后面会专门讲到Method结构,要求方法长度是[0,numM),也就是不限制类型
	Method(int) Method
	MethodByName(string) (Method, bool)
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string
	PkgPath() string
	
	// 大小,不需要care
	Size() uintptr

	// 类似于Java的ToString
	String() string

	// Kind returns the specific kind of this type.
  // 很重要,边界多依靠kind进行区分,返回该对象类型,比如指针,切片,结构体。。。。
	Kind() Kind

  // 是否实现了某个接口
	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

  // 是否能转换
	// ConvertibleTo reports whether a value of the type is convertible to type u.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	Comparable() bool

	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// 首先得是一个fun,判断是不是可变参数
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.(记得interface讲过,有些时候会有包装类型)
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type

	// 字段信息
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

  // 嵌套,比如field(1)为结构体,进入这个结构体,就需要这个[1,1],就是这个结构体的字段一
	FieldByIndex(index []int) StructField

	FieldByName(name string) (StructField, bool)

  // 回掉,filter
	FieldByNameFunc(match func(string) bool) (StructField, bool)

    // 函数的参数类型
    // It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// 返回map对象的key类型
	// It panics if the type's Kind is not Map.
	Key() Type

	// 返回数组长度
	// It panics if the type's Kind is not Array.
	Len() int

	// 返回结构体的字段数
	// It panics if the type's Kind is not Struct.
	NumField() int

	// 函数的参数个数
	// It panics if the type's Kind is not Func.
	NumIn() int

	// 函数的返回值个数
	// It panics if the type's Kind is not Func.
	NumOut() int

	// 函数的输出类型
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}
复制代码

如何使用

大致介绍一下:

​ 三个测试对象,后面也有用到的

type UserService interface {
	Service(str string) string
}

type userService struct {
	ServerName string
	Info       map[string]interface{}
	List       [10]int
}

func (*userService) Service(str string) string {
	return "name"
}
复制代码

如何安全使用呢

func TestUserServer(t *testing.T) {
	var in = (*UserService)(nil) //1、接口
	inter := reflect.TypeOf(in)
	if inter.Kind() == reflect.Ptr { // 2、判断是不是指针,拿到内部的元素
		inter = inter.Elem()
	}
	if inter.Kind() != reflect.Interface {
		panic("this service not interface")
	}

	service := new(userService)
	tp := reflect.TypeOf(service)
	if tp.Kind() == reflect.Ptr {
		method := tp.NumMethod() // 获取方法
		for x := 0; x < method; x++ {
			fmt.Printf("%+v/n", tp.Method(x))
		}
		if tp.Implements(inter) { // 判断是否实现了某个接口
			fmt.Printf("%s implatement %s/n", tp, inter)
		}
		tp = tp.Elem()
	}

	if tp.Kind() == reflect.Struct { //
		fieldN := tp.NumField()
		for x := 0; x < fieldN; x++ { // 获取字段信息
			fmt.Printf("%+v/n", tp.Field(x))

			if tp.Field(x).Type.Kind() == reflect.Map { // 如果是map,可以获取key元素
				fmt.Printf("FieldName: %s, key: %s./n", tp.Field(x).Name, tp.Field(x).Type.Key().Kind())
			}
			if tp.Field(x).Type.Kind() == reflect.Array { // 如果是数组,可以获取长度信息
				fmt.Printf("FieldName: %s, len: %d./n", tp.Field(x).Name, tp.Field(x).Type.Len())
			}
		}
	}
}
复制代码

Value

结构体

​ Value 可以说桥接着 我们的数据和元信息的桥梁,但是正因为隔了桥,Value主要是理解方法的使用

type Value struct {
	// typ holds the type of the value represented by a Value.
	typ *rtype // 可以理解为iface的  type 
	// Pointer-valued data or, if flagIndir is set, pointer to data.
	// Valid when either flagIndir is set or typ.pointers() is true.
	ptr unsafe.Pointer // 可以理解为iface 的 data
	// flag holds metadata about the value.
	flag
}
复制代码

主要操作

Addr 方法

可以理解为比如我们的对象是一个非指针类型,现在想要一个指针的咋办,就需要使用 Addr() ,其实说就是这个意思,但是需要注意的是必须要 CanAddr() 才可以进行转换的,补充一下其实就是必须一下我这种玩法。说实话感觉没啥用。

fmt.Println(reflect.ValueOf(&userService{}).CanAddr()) // 所有的 reflect.ValueOf()都不可以直接拿到addr()
fmt.Println(reflect.ValueOf(userService{}).CanAddr())

// addr 的作用
func TestAddr(t *testing.T) {
	x := 2
	d := reflect.ValueOf(&x)
	value := d.Elem().Addr().Interface().(*int) // 可以直接转换为指针
	*value = 1000
	fmt.Println(x) // "3"
}
复制代码

Set 方法

这个方法比较有用,调用的时候注意需要使用 reflect.CanSet() 判断下,我下面写法其实是不对的。

func TestDemos(t *testing.T) {
	x := 2
	d := reflect.ValueOf(&x)
	d.Elem().SetInt(1000)
	fmt.Println(x) // 1000
}
复制代码

Elem

​ Elem returns the value that the interface v contains or that the pointer v points to.
​ 主要是返回接口真正包含的内容或者指针正在指向的位置。所以掉用的时候,最好进行类型判断
func TestElem(t *testing.T) {
	x := 2
	d := reflect.ValueOf(&x)
	if d.Kind() == reflect.Ptr {
		d.Elem() // 调用elem 获取指针真正指向的对象
	}

	// 或者,可以调用这个方法安全的调用
	d=reflect.Indirect(d)
}
复制代码

New & Set (十分重要)

​ 有些时候,我们拿到类型,想要实例化一个对象,如何呢,就需要使用这个了,这类方法有很多,newslice,newarr等, 注意 reflect.New() 返回的类型是指向类型的指针,比如type=string,此时生成的对象是type=*string

调用Set的时候,必须是先调用 CanSet() ,判断是否可以设置,基本上每一个Value对象初始化的时候都不能CanSet。

func TestElem(t *testing.T) {
	value := reflect.New(reflect.TypeOf("111")) // 初始化一个 string类型的value,但是需要注意的是初始化完成后是 *string,任何类型都是,New()方法调用完成后都会是一个指针指向原来的类型数据,也就是多了个*
	fmt.Println(value.Kind())       // 因此这里输出的是 *string ,ptr
	value = reflect.Indirect(value) // 获取真正的类型,string, 
	fmt.Println(value.Kind()) // 
	if value.CanSet() {
		value.SetString("hello world") // set string,必须要求类型是string的,而且can set,
	}
	fmt.Println(value.Interface().(string)) // "hello world"
}
复制代码

注意点一

reflect.New() 方法千万不要new 一个指针类型

以初始化一个结构体为例子:

// 错误写法
func main() {
  // reflect.TypeOf(new(api.User)) 类型为 *api.User
  // reflect.New(reflect.TypeOf(new(api.User))) 语意是:初始化一个x=(*api.User)(nil)数据,返回值为&x,所以最终的返回类型是**api.User,值为nil的数据
	value := reflect.New(reflect.TypeOf(new(api.User)))
	fmt.Println(value.String()) //<**api.User Value>
  // value.Elem() 类型为*api.User
	fmt.Printf("%+v", value.Elem().Interface().(*api.User)) // nil
}


// 正确做法
func main() {
	// reflect.TypeOf(new(api.User)) 类型是 *api.User
	// reflect.TypeOf(new(api.User)).Elem() 类型是 api.User
	// reflect.New(reflect.TypeOf(new(api.User)).Elem()) 的含义是初始化一个api.User类型的数据,返回&api.User,所以最终类型是 *api.User
	value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
	fmt.Println(value.String())           // <*api.User Value>
	user := value.Interface().(*api.User) // 所以类型是 *api.User, 没毛病
	user.Name = "tom" // 
	nuser := value.Elem().Interface().(api.User) // 拿到 api.User类型,设置一下试试
	nuser.Age = 10
	fmt.Printf("%+v", value.Interface().(*api.User)) //&{Name:tom Age:0} ,所以拿到指针数据就可以进行赋值修改了,但是切记不能拿struct类型进行修改,不然修改无效
}
复制代码

​ 根据以上例子,希望让大家明白,new 一个指针的危害性,所以开发中切记别new 一个type=ptr的数据。

注意点二

value.Set() 方法set数据的时候,value的类型不能是指针类型,虽然可以用 value.CanSet() 可以判断(其实它多为字段是否可以set的时候进行判断),但是毕竟我们要拿到值进行设置数据的,不一定的字段。

// 错误写法
func main() {
	value := reflect.New(reflect.TypeOf(new(api.User)).Elem()) // value 类型为 *api.User
	if value.Kind()==reflect.Ptr { // 指针类型 没问题,我们就去设置一个指针类型的数据吧
		value.Set(reflect.ValueOf(&api.User{})) // 设置一个 *api.User的数据,发现panic了
	}
}
// panic: reflect: reflect.flag.mustBeAssignable using unaddressable value


// 正确写法
func main() {
	value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
	if value.Kind() == reflect.Ptr {
		value = value.Elem()
	}
	if value.CanSet() {
		value.Set(reflect.ValueOf(api.User{}))
	}
	fmt.Printf("%+v",value.Interface().(api.User))
}
// 输出: {Name: Age:0}
复制代码

所以说它常用来进行

type User struct {
	Name string
	Age  int
	birthday time.Time //这个不可见
}

func test() {
	user := api.User{}
	value := reflect.ValueOf(&user)
	fmt.Println(value.String())
	for value.Kind() == reflect.Ptr { // 是指针类型,就去拿到真正的类型
		value = value.Elem()
	}
	if value.Kind() == reflect.Struct {// 如果是struct类型,就可以去拿字段
		birthDay := value.FieldByName("birthday") // 这个显然不能set
		if birthDay.CanSet() {
			birthDay.Set(reflect.ValueOf(time.Now()))
		}
		name := value.FieldByName("Name") // 这个可以
		if name.CanSet() {
			name.Set(reflect.ValueOf("tom"))
		}
	}
	fmt.Printf("%+v", user) // {Name:tom Age:0 birthday:{wall:0 ext:0 loc:<nil>}}
}
复制代码

Call

​ 这个是调用方法的,类似于Java的 Method.Invoke() ,其实这种玩法很不推荐,我们知道golang,对于方法是很随意的,各种类型都可以定义方法,所以主流的rpc语言都是使用的接口约束 Method 信息,进而获取类型。后期我会解读go-rpc,它自带的rpc框架内部实现.

func TestCall(t *testing.T) {
	value := reflect.ValueOf(new(userService))
	if value.NumMethod() > 0 {
		fmt.Println(value.NumMethod()) // 1
		method := value.MethodByName("Service")
		fmt.Println(method.Kind()) // "func"
		method.Call([]reflect.Value{reflect.ValueOf("hello world")}) // hello world
	}
}
复制代码

Field

​ 一些字段的元信息 ,比如 tag 信息,字段名称,字段类型等

func TestDemos(t *testing.T) {
	tp := reflect.TypeOf(new(userService))

	if tp.Kind() == reflect.Ptr {
		tp = tp.Elem()
	}

	if tp.Kind() != reflect.Struct {
		t.Fatal("not support")
	}

	field, _ := tp.FieldByName("ServerName") // 不许是struct 类型
	fmt.Printf("FieldTag json=%s/n",field.Tag.Get("json"))
	fmt.Printf("FieldName=%s/n",field.Name)
}
复制代码

Method

​ 首先要清楚Method包含哪些信息,其中特别要注意这里的Func 于 reflect.MethodByName() 此类方法获取的Method区别,前者的第一个参数是调用放(也就是this),后者的第一个参数直接是第一个参数,而且后者的类型是 reflect.Value

type Method struct {
	Name    string // 方法名
	PkgPath string
	Type  Type  // method type 方法类型
	Func  Value // func with receiver as first argument,以接收者为第一个参数,也就是调用者
	Index int   // index for Type.Method,第几个方法
}
复制代码

面向接口开发

type MethodMeta struct {
	obj     reflect.Value // 调用者
	Method  reflect.Method// method信息
	InType  []reflect.Type // 输入类型
	OutType [] reflect.Type// 接受类型
}
复制代码

获取完毕,如果我们要调用这个方法怎么办

​ 解释一下为啥要参数传递是一个接口呢,接口的好处就是类型约定,go里面任意类型都可以实现方法,所以对于这种问题,是很头痛的,主流的rpc框架这部分实现都是基于接口级别的。

func Proxy(service UserService) *MethodMeta {
	val := reflect.TypeOf(service)
	method, _ := val.MethodByName("Service") // 获取方法
	meta := MethodMeta{}
	meta.Method = method // 方法的原信息
	tt := method.Type    // 方法类型
	{
		in := tt.NumIn()
		meta.InType = make([]reflect.Type, in)
		for x := 1; x < in; x++ { // 0号元素是调用方,所以只需要记录参数,所以需要变动下
			meta.InType[x-1] = tt.In(x)
		}

	}
	{
		in := tt.NumOut()
		meta.OutType = make([]reflect.Type, in)
		for x := 0; x < in; x++ {
			meta.OutType[x] = tt.Out(x)
		}
	}
	meta.obj = reflect.ValueOf(service)
	return &meta
}
复制代码

Demo

func BenchmarkCall(b *testing.B) {
	b.SetParallelism(1)
	proxy := Proxy(new(userService))
	for i := 0; i < b.N; i++ {
		value := reflect.New(proxy.InType[0]).Elem() // new 传入类型
		if value.CanSet() {
			value.SetString("11111")
		}
		call := proxy.Method.Func.Call([]reflect.Value{proxy.obj, value}) // 调用函数,回去返回类型
		_ = call[0].Interface() // 获取真正的类型
	}
}
// goos: darwin
// goarch: amd64
// pkg: go-src-demo/insafe/test
//BenchmarkCall-8   	 2672127	       442 ns/op
//PASS

func BenchmarkInvoke(b *testing.B) {
	b.SetParallelism(1)
	proxy :=new(userService)
	for i := 0; i < b.N; i++ {
		proxy.Service("111")
	}
}
// BenchmarkInvoke-8   	1000000000	         0.338 ns/op 
复制代码

大家可以看看反射调用的时间是多久倍? 大约是差距上万倍的效率,可能还会更高。 ns级别说实话这个还可以接受。

Gob && Json 序列化

目前主流的rpc框架都是采用的自定义的编解码机制,依靠接口实现来进行实现的,比如统一实现 RequestResponse 接口,提供了编解码入口。

那么Go也提供了多种的编解码机制,golang底层的gob支持以Value的方式进行编解码,类似于Java的 Serializable 接口的内置序列化,不适合跨语言。 但是Json、xml等都可以跨语言进行使用。

gob 编解码

func TestGob(t *testing.T) {
	user := User{
		Name: "tom",
		Age:  1,
	}
	// 编码,这个编码不是普通的json编码,而是具有特殊含义的
	buffer := bytes.Buffer{}
	err := gob.NewEncoder(&buffer).Encode(user) // 编码
	if err != nil {
		t.Fatal(err)
	}
	// 解码,也是,支持使用Value的方式解码
	of := reflect.TypeOf(new(User))
	var value reflect.Value
	if of.Kind() == reflect.Ptr {
		value = reflect.New(of.Elem()) // 反射实例化一个对象
	}
	err = gob.NewDecoder(&buffer).DecodeValue(value)
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println(value.Interface().(*User)) // 成功解码
}
复制代码

json 编解码

func TestJson(t *testing.T) {
	user := User{
		Name: "tom",
		Age:  1,
	}
	buffer := bytes.Buffer{}
	err := json.NewEncoder(&buffer).Encode(user) //json编码
	if err != nil {
		t.Fatal(err)
	}
	of := reflect.TypeOf(new(User))
	var value reflect.Value
	if of.Kind() == reflect.Ptr {
		value = reflect.New(of.Elem())
	}
	err = json.NewDecoder(&buffer).Decode(value.Interface()) //json解码
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println(value.Interface().(*User)) //打印数据
}
复制代码

效率的化,json的速度远远高于gob

BenchmarkName-8   	  530700	      1984 ns/op  //json
BenchmarkName-8   	   44244	     26257 ns/op  //gob
复制代码

MakeFunc

reflect.MakeFunc 这个函数记住第一个Type类型必须是Func,其次这个Func它只需要它的方法名称,方法传入、传出类型。

type Model interface {
	TableName() string
}

func TestEcho(t *testing.T) {
	fun := (*Model)(nil)
	tp := reflect.TypeOf(fun).Elem()
	if tp.NumMethod() > 0 {
		method, _ := tp.MethodByName("TableName")
		if method.Type.Kind() == reflect.Func {
			makeFunc := reflect.MakeFunc(method.Type, func(args []reflect.Value) (results []reflect.Value) {
				return []reflect.Value{reflect.ValueOf("student")}
			})
			fmt.Println(makeFunc.Call([]reflect.Value{}))
		}
	}
}
复制代码

说句实在话,这个玩意没啥用,无人使用,第一没有方法调用效率高,第二真的啥也做不了。

欢迎关注我们的微信公众号,每天学习Go知识

Go reflect 反射- Type &amp; Value &amp; Field &amp; Method
原文  https://studygolang.com/articles/29541
正文到此结束
Loading...