23

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

 4 years ago
source link: https://studygolang.com/articles/29541
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

其实 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知识

FveQFjN.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK