其实Type
和 Value
本质就是对于Golang底层数据的一个封装罢了,其实就是基于iface和eface进行可以编程级别的开发,因为那俩对象对于开发者来说属于黑盒子。
为什么我多拿出Field
和 Method
本质上为了体现这俩的重要性,也会着重讲到
反射的几个重点: 1、安全的使用反射 2、依靠
Type
可以生成对于类型的数据,核心在于New
和Set
方法 3、理解Type
和Value
的用法 4、熟练的掌握Call
函数,进行安全操作 5、理解api的使用,熟练掌握 6、学会使用反射进行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>}}
}
复制代码
其中还有一个点是:(如何给interface 赋值)
如何安全的赋值
// inter must a ptr
func SetInterface(dest interface{}, src interface{}) error {
if dest == nil || src == nil {
return nil
}
value := reflect.ValueOf(dest)
kind := value.Kind()
_, isExist := isNilKind[kind]
if isExist && value.IsNil() {
return errors.New("the dest is nil")
}
if kind != reflect.Ptr {
return errors.New("the dest must a ptr")
}
srcValue := reflect.ValueOf(src)
// 如果 src也是指针,那就取elem
if value.Type() == srcValue.Type() {
srcValue = srcValue.Elem()
}
elem := value.Elem()
if elem.CanSet() {
if elem.Type() == srcValue.Type() {
elem.Set(srcValue)
}
}
return nil
}
复制代码
Call(相当重要)
这个是调用方法的,类似于Java的
Method.Invoke()
,其实这种玩法很不推荐,我们知道golang,对于方法是很随意的,各种类型都可以定义方法,所以主流的rpc语言都是使用的接口约束Method
信息,进而获取类型。后期我会解读go-rpc,它自带的rpc框架内部实现. 区别就是
reflect.ValueOf().Call()
和reflect.TypeOf().Method().Func.Call()
这俩call方法不同的是,前面那个不需要传递 receiver,后者的第一个参数必须是receiver
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
}
}
复制代码
IsValid
// isValid 是判断一个value 是不是有值
func TestIsValid(t *testing.T) {
of := reflect.ValueOf(nil)
fmt.Println(of.IsValid()) // false
}
// IsValid reports whether v represents a value. 代表v表示一个值(nil显示不是)
// It returns false if v is the zero Value.(false 是 v是一个空)
复制代码
IsNil
主要是判断包装类型,是不是被指针封装的对象,是不是空。但是容易因为类型panic
// ok 没问题
func TestIsNil(t *testing.T) {
i := (*error)(nil)
value := reflect.ValueOf(i)
fmt.Println(value.IsValid()) // true ,显示不是空
fmt.Println(value.IsNil()) // true , 因为它确实是nil
}
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics.
// 正确用法
func TestIsNil2(t *testing.T) {
var (
isNilKind = map[reflect.Kind]interface{}{
reflect.Chan: nil,
reflect.Func: nil,
reflect.Map: nil,
reflect.Ptr: nil,
reflect.UnsafePointer: nil,
reflect.Interface: nil,
reflect.Slice: nil,
}
)
value := reflect.ValueOf(1234)
_, isExist := isNilKind[value.Kind()]
if isExist {
fmt.Println(value.IsNil()) // 调用这个需要前置的判断条件,就是kind
}
}
复制代码
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框架都是采用的自定义的编解码机制,依靠接口实现来进行实现的,比如统一实现Request
和Response
接口,提供了编解码入口。
那么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{}))
}
}
}
复制代码
说句实在话,这个玩意没啥用,无人使用,第一没有方法调用效率高,第二真的啥也做不了。
参考
有疑问加站长微信联系(非本文作者)