其实 Type
和 Value
本质就是对于Golang底层数据的一个封装罢了,其实就是基于iface和eface进行可以编程级别的开发,因为那俩对象对于开发者来说属于黑盒子。 为什么我多拿出 Field
和 Method
本质上为了体现这俩的重要性,也会着重讲到。
反射的几个重点: 1、安全的使用反射 2、依靠 Type
可以生成对于类型的数据,核心在于 New
和 Set
方法 3、理解 Type
和 Value
的用法 4、理解api的使用,熟练掌握 5、学会使用反射进行 IOC
操作
Type 也就是元信息,类似于Java的Class对象,拿到Type基本可以做所有的一切,包含结构信息,字段信息,方法信息等等,所以这个是重点,Java有的功能Go基本都有,唯一区别的是字节码动态修改注入,编译性语言必然缺失的一部分,因为所谓的类型都是程序启动前都确定好的,不可修改的,这部分内容是最重要的。
还要需要补充的,使用reflect包,需要核心关注边界的点,必须注意,也是最为核心关注的,因为有些方法调用是有条件的。
核心关注与边界,官方的注释很详细,如果想要使用哪个方法需要看清楚使用边界
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主要是理解方法的使用
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()
,其实说就是这个意思,但是需要注意的是必须要 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" } 复制代码
这个方法比较有用,调用的时候注意需要使用 reflect.CanSet()
判断下,我下面写法其实是不对的。
func TestDemos(t *testing.T) { x := 2 d := reflect.ValueOf(&x) d.Elem().SetInt(1000) fmt.Println(x) // 1000 } 复制代码
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) } 复制代码
有些时候,我们拿到类型,想要实例化一个对象,如何呢,就需要使用这个了,这类方法有很多,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>}} } 复制代码
这个是调用方法的,类似于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 } } 复制代码
一些字段的元信息 ,比如 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包含哪些信息,其中特别要注意这里的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级别说实话这个还可以接受。
目前主流的rpc框架都是采用的自定义的编解码机制,依靠接口实现来进行实现的,比如统一实现 Request
和 Response
接口,提供了编解码入口。
那么Go也提供了多种的编解码机制,golang底层的gob支持以Value的方式进行编解码,类似于Java的 Serializable
接口的内置序列化,不适合跨语言。 但是Json、xml等都可以跨语言进行使用。
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)) // 成功解码 } 复制代码
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 复制代码
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知识