golang中reflect使用


原文链接: golang中reflect使用

获取ptr的大小,操作系统指针大小

const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const

  1. uintptr(0) 将 0 转换为 uintptr 类型
  2. ^uintptr(0) 对 0 进行取反 : 32为 0xFFFF FFFFF FFFF FFFF 64位为 0xFFFF FFFFF FFFF FFFF FFFF FFFFF FFFF FFFF
  3. (^uintptr(0) >> 63) 左移 63 位 : 32 位系统为0; 64位系统为1
  4. 4 << (^uintptr(0) >> 63) 所以结果就是 32位系统 指针大小为4 64位系统ptr大小为8

reflect.Indirect(v) 是 v.Elem() 的升级版.

func Indirect(v Value) Value {
	if v.Kind() != Ptr {
		return v
	}
	return v.Elem()
}
// Elem()只负责解析 指针和空接口
func (v Value) Elem() Value {
	k := v.kind()
	switch k {
    case Interface:
    ...
		return unpackEface(eface)
    case Ptr:
    ...
		return Value{typ, ptr, fl}
    }
    panic(v.kind())
}

reflect.Indirect(v) 和 v.Elem(v) 区别:
如果reflect.Value是一个指针*p,

1.1  那么v.Elem()等价于reflect.Indirect(v)

如果reflect.Value不是指针

2.1 如果是interface, 那么reflect.Indirect(v)返回同样的值,而v.Elem()返回接口的动态的值
2.2 如果是其它值, v.Elem()会panic,而reflect.Indirect(v)返回原值

reflect包的两个重要的函数: TypeOf(i) ValueOf(i)

  1. reflect.TypeOf(i interface{}) Type
  2. reflect.ValueOf(i interface{}) Value
    因为接收的参数是空接口interface{}类型,因此可以接收任意类型的数据。
    TypeOf()的返回值是这个接口类型对应的reflect.Type对象。通过Type提供的一些方法,就可以获得这个接口实际的静态类型。

reflect包的两个重要的类型 reflect.Type 和 reflect.Value

这两种类型reflect.Typereflect.Value都提供了大量的方法让我们可以检查和操作这两种类型

共同点: 他们都有 Kind() 方法
.Kind()方法reflect.Typereflect.Value都有,返回一个const 它是对象的基本类型,表示具体类型的底层类型, 共27种 如 Bool Int8 Uintptr Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer 等,而不是静态类型User(自定义类型通过Type()获取,只有Value对象有)。

不同点: Type,Value分别对应对象的类型和值数据
Type interface{} 是一个接口:可以表示一个Go类型

reflect.Type是一个接口类型的对象,这个接口包含了很多方法,像Name(),Field(),Method()等
1. Elem() Type 方法返回 Array、Chan、Slice、Map、Ptr的基类型; 对指针类型的数据"解引用"
可用反射提取struct tag,还能自动分解,常用于ORM映射、数据验证等;
辅助判断方法Implements()、ConvertibleTo()、AssignableTo()

Value struct{} 是一个结构体:可以持有一个任意类型的值

1. Elem() Value 方法返回 Interface、Ptr; 
2. Type() 方法将返回具体类型所对应的自定义类型 如 `User`
3. Interface() 方法是`reflact.ValueOf()`方法的逆,它把一个reflect.Value恢复成一个接口值,把Value中保存的类型和值的信息打包成一个接口表示并返回;如:
    `y,ok := v.Interface().(float64)` // y 的类型被断言为 float64
    `fmt.Println(y)`
    以上可简写为这样:
    `fmt.Println(v.Interface())` //fmt.Println会把它恢复出来
chan类型的反射对象:有TrySend()、TryRecv()方法;
IsNil()方法判断反射对象保存的值是否为nil;

Kind()返回的27种类型:

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)
func (elt ZeroIsNullMeddler) PostRead(fieldAddr, scanTarget interface{}) error {
	sv := reflect.ValueOf(scanTarget)
	fv := reflect.ValueOf(fieldAddr)
	if sv.Elem().IsNil() {
		// null column, so set target to be zero value
		fv.Elem().Set(reflect.Zero(fv.Elem().Type()))
	} else {
		// copy the value that scan found
		fv.Elem().Set(sv.Elem().Elem())
	}
	return nil
}

func (elt ZeroIsNullMeddler) PreWrite(field interface{}) (saveValue interface{}, err error) {
	val := reflect.ValueOf(field)
	switch val.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if val.Int() == 0 {
			return nil, nil
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if val.Uint() == 0 {
			return nil, nil
		}
	case reflect.Float32, reflect.Float64:
		if val.Float() == 0 {
			return nil, nil
		}
	case reflect.Complex64, reflect.Complex128:
		if val.Complex() == 0 {
			return nil, nil
		}
	case reflect.String:
		if val.String() == "" {
			return nil, nil
		}
	default:
		return nil, fmt.Errorf("ZeroIsNullMeddler.PreWrite: unknown struct field type: %T", field)
	}

	return field, nil
}
list := []*model.File{}
err := meddler.QueryAll(db, &list, stmt, build.ID)
func  QueryAll(db DB, dst interface{}, query string, args ...interface{}) error {
	return d.ScanAll(rows, dst)
}
// ScanAll scans all sql result rows into a slice of structs.
// dst should be a pointer to a slice of the appropriate type.
func (d *Database) ScanAll(rows *sql.Rows, dst interface{}) error {
	// make sure we always close rows
	defer rows.Close()

	// make sure dst is an appropriate type
	dstVal := reflect.ValueOf(dst)
	if dstVal.Kind() != reflect.Ptr || dstVal.IsNil() {
		return fmt.Errorf("ScanAll called with non-pointer destination: %T", dst)
	}
	sliceVal := dstVal.Elem()
	if sliceVal.Kind() != reflect.Slice {
		return fmt.Errorf("ScanAll called with pointer to non-slice: %T", dst)
	}
	sliceVal.Elem()
	ptrType := sliceVal.Type().Elem()
	if ptrType.Kind() != reflect.Ptr {
		return fmt.Errorf("ScanAll expects element to be pointers, found %T", dst)
	}
	eltType := ptrType.Elem()
	if eltType.Kind() != reflect.Struct {
		return fmt.Errorf("ScanAll expects element to be pointers to structs, found %T", dst)
	}
// gather the results
	for {
		// create a new element
		eltVal := reflect.New(eltType)  // 通过反射创建Struct对象,返回值为reflect.Value类型
		elt := eltVal.Interface()       // 将Struct对象转为接口类型

		// scan it
		if err := d.scanRow(data, rows, elt, columns); err != nil {
			if err == sql.ErrNoRows {
				return nil
			}
			return err
		}

		// add to the result slice
		sliceVal.Set(reflect.Append(sliceVal, eltVal))
	}
}

reflect包是golang中很重要的一个包,实现了在运行时允许程序操纵任意类型对象的功能。可以看下文档简单了解一下。

在reflect中,最重要的是Value类,只有先获取到一个对象或者变量的Value对象后,我们才可以对这个对象或者变量进行更进一步的分析和处理。我们可以使用reflect.ValueOf()方法获取Value对象。

1.从interface{}接口变量中获取value和type信息

package main
import (
	"fmt"
	"reflect"
)
func main() {
	var x float64 = 3.4
	fmt.Println("type: ", reflect.TypeOf(x))  //type:  float64
    fmt.Println("type: ", reflect.ValueOf(x)) //type:  3.4
}

2.从Value中获取接口信息

类型为”relfect.Value”变量,通过下面的方法可以获得接口变量:

func (v Value) Interface() interface{}

当收到一个类型为reflect.Value类型的变量时,用下面方式将它转换对应的接口变量,然后进行类型判断:

y := v.Interface().(float64)

之后就可以使用y的成员和方法。

3.通过reflect.Value设置实际变量的值

reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值。

例如:

var x float64 = 3.4
p := reflect.ValueOf(&x)    // Note: take the address of x.
v := p.Elem()
fmt.Println("type of p:", v.Type())
fmt.Println("settability of p:", v.CanSet())
v.SetFloat(77)

传入的是 *float64,需要用p.Elem()获取所指向的Value。v.CantSet()输出的是true,因此可以用v.SetFloat()修改x的值。
收到reflect.Value变量后
如果得到了一个类型为reflect.Value的变量,可以通过下面的方式,获得变量的信息。
如果知道v的真实类型,可先转换成interface{},然后转化成对应的类型:
r := v.Interface().(已知的类型)
除了interface{},还可以转换成其它类型:

func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Int() int64
func (v Value) Uint() uint64
...

如果不知道v的真实类型,获取它的Type,然后遍历Type的Field,和v的Field:

t := v.Type()
for i := 0; i < v.NumField(); i++ {
	f := v.Field(i)
	fmt.Printf("%d: %s %s = %v\n", i, t.Field(i).Name, f.Type(), f.Interface())
}

1.获取对象或者变量的类型 [Value.Type()和Value.Kind()] 区别

Value.Type() 和 Value.Kind() 这两个方法都可以获取对象或者变量的类型,如果是变量的话,使用这两个方法获取到的类型都是一样,差别是结构体对象
举个例子看一下:

var i int
value := reflect.ValueOf(i)

log.Println(value.Type()) //输出:int
log.Println(value.Kind()) //输出:int

type S struct {
    a string
}

var s S
value2 := reflect.ValueOf(s) // 使用ValueOf()获取到结构体的Value对象

log.Println(value2.Type()) //输出:S
log.Println(value2.Kind()) //输出:struct

变量i使用kind和type两个方法都输出了int,而结构体s的Type()方法输出了S,Kind()方法输出了struct,由此可以总结如下,如果你想拿到结构体里面定义的变量信息的时候,使用Type(f)方法。如果只是相判断是否是结构体时,就使用Kind()

2.获取变量的值,修改变量值

获取变量的值使用value.Interface()方法,该方法会返回一个value的值,不过类型是interface。给变量赋值需要先判断该变量的类型,使用之前提到过的Value.Kind()方法,如果变量的类型是reflect.Int,我们就可以使用Value.SetInt()方法给变量赋值。下面是一个例子:

var i int = 1
// 获取Value,这里注意,如果你要改变这个变量的话,需要传递变量的地址
value := reflect.ValueOf(&i)
// value是一个指针,这里获取了该指针指向的值,相当于value.Elem()
value = reflect.Indirect(value)
// Interface是获取该value的值,返回的是一个interface对象
log.Println(value.Interface()) // 输出:1

// 把变量i的值设为2
if value.Kind() == reflect.Int {
    value.SetInt(2)
}
log.Println(value.Interface()) // 输出:2

给结构体对象中的成员变量赋值的方法:

type S struct {
    A string // 注意:只有大写开头的成员变量可以Set
}
s := S{"x"}
value := reflect.ValueOf(&s)
value = reflect.Indirect(value)
//value是结构体s,所以打印出来的是整个结构体的信息
log.Println(value.Interface()) //输出: {x}

f0 := value.FieldByName("A") //获取结构体s中第一个元素a
log.Println(f0) // 输出: x
if f0.Kind() == reflect.String {
    if f0.CanSet() {
        f0.SetString("y")
    }
}

log.Println(f0) // 输出: y
log.Println(value.Interface()) //输出: {y}

结构体这里需要注意的是,只有公有的成员变量可以被reflect改变值,私有的变量是无法改变值得。

3.获取结构体成员变量的tag信息

由于golang变量大小写和公有私有息息相关,所以码农门很难按照自己的意愿来定义变量名。于是golang提供了tag机制,来给变量提供一个标签,这个标签可以作为一个别名,来给一些存储结构来获取结构体变量名字使用。下面是一个获取结构体成员变量tag信息的例子:

type S struct {
    A string `json:"tag_a"`
}

s := S{}

value := reflect.ValueOf(&s)
value = reflect.Indirect(value)

//获取结构体s的类型S
vt := value.Type()
//获取S中的A成员变量
f, _ := vt.FieldByName("A")

//获取成员变量A的db标签
log.Println(f.Tag.Get("json")) //输出: tag_a

reflect库的godoc在http://golang.org/pkg/reflect/

reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。

Type就是定义的类型的一个数据类型,Value是值的类型

package main

import (
    "fmt"
    "reflect"
)
type MyStruct struct {
    name string
}

func (this *MyStruct) GetName(str string) string {
    this.name = str
    return this.name
}

func main() {

    // 备注: reflect.Indirect -> 如果是指针则返回 Elem()
    // 首先,reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。
    // Type就是定义的类型的一个数据类型,Value是值的类型

    // 对象
    s := "this is string"

    // 获取对象类型 (string)
    fmt.Println(reflect.TypeOf(s))

    // 获取对象值 (this is string)
    fmt.Println(reflect.ValueOf(s))

    // 对象
    var x float64 = 3.4

    // 获取对象值 (<float64 Value>)
    fmt.Println(reflect.ValueOf(x))

    // 对象
    a := &MyStruct{name: "nljb"}

    // 返回对象的方法的数量 (1)
    fmt.Println(reflect.TypeOf(a).NumMethod())

    // 遍历对象中的方法
    for m := 0; m < reflect.TypeOf(a).NumMethod(); m++ {
        method := reflect.TypeOf(a).Method(m)
        fmt.Println(method.Type)         // func(*main.MyStruct) string
        fmt.Println(method.Name)         // GetName
        fmt.Println(method.Type.NumIn()) // 参数个数
        fmt.Println(method.Type.In(1))   // 参数类型
    }

    // 获取对象值 (<*main.MyStruct Value>)
    fmt.Println(reflect.ValueOf(a))

    // 获取对象名称
    fmt.Println(reflect.Indirect(reflect.ValueOf(a)).Type().Name())

    // 参数
    i := "Hello"
    v := make([]reflect.Value, 0)
    v = append(v, reflect.ValueOf(i))

    // 通过对象值中的方法名称调用方法 ([nljb]) (返回数组因为Go支持多值返回)
    fmt.Println(reflect.ValueOf(a).MethodByName("GetName").Call(v))

    // 通过对值中的子对象名称获取值 (nljb)
    fmt.Println(reflect.Indirect(reflect.ValueOf(a)).FieldByName("name"))

    // 是否可以改变这个值 (false)
    fmt.Println(reflect.Indirect(reflect.ValueOf(a)).FieldByName("name").CanSet())

    // 是否可以改变这个值 (true)
    fmt.Println(reflect.Indirect(reflect.ValueOf(&(a.name))).CanSet())

    // 不可改变 (false)
    fmt.Println(reflect.Indirect(reflect.ValueOf(s)).CanSet())

    // 可以改变
    // reflect.Indirect(reflect.ValueOf(&s)).SetString("jbnl")
    fmt.Println(reflect.Indirect(reflect.ValueOf(&s)).CanSet())

}

获取 Struct 对象的 Tag

type Home struct {
    i int `json:"100"`
    j int `json:"200"`
}

func main() {
    home := new(Home)
    home.i = 5
    rcvr := reflect.ValueOf(home)
    typ := reflect.Indirect(rcvr).Type()
    fmt.Println(typ.Kind().String())
    x := typ.NumField()
    for i := 0; i < x; i++ {
        json := typ.Field(i).Tag.Get("json")
        fmt.Println(json)
    }
}

反射使用案例

package server

import (
    "fmt"
    "net/http"
    "reflect"
    "strings"
)

type Server struct {
    name    string
    rcvr    reflect.Value
    typ     reflect.Type
    methods map[string]*Method
}

type Method struct {
    method reflect.Method
    json   bool
}

func NewServer() *Server {
    server := new(Server)
    server.methods = make(map[string]*Method)
    return server
}

func (this *Server) Start(port string) error {
    return http.ListenAndServe(port, this)
}

func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    for mname, mmethod := range this.methods {
        if strings.ToLower("/"+this.name+"."+mname) == r.URL.Path {
            if mmethod.json {
                returnValues := mmethod.method.Func.Call(
                    []reflect.Value{this.rcvr, reflect.ValueOf(w), reflect.ValueOf(r)})
                content := returnValues[0].Interface()
                if content != nil {
                    w.WriteHeader(500)
                    ...
                }
            } else {
                mmethod.method.Func.Call(
                    []reflect.Value{this.rcvr, reflect.ValueOf(w), reflect.ValueOf(r)})
            }
        }
    }
}

/*
    func (this *Hello) JsonHello(r *http.Request) {}
    func (this *Hello) Hello(w http.ResponseWriter, r *http.Request) {}
*/
func (this *Server) Register(rcvr interface{}) error {
    this.typ = reflect.TypeOf(rcvr)
    this.rcvr = reflect.ValueOf(rcvr)
    this.name = reflect.Indirect(this.rcvr).Type().Name()
    if this.name == "" {
        return fmt.Errorf("no service name for type ", this.typ.String())
    }
    for m := 0; m < this.typ.NumMethod(); m++ {
        method := this.typ.Method(m)
        mtype := method.Type
        mname := method.Name
        if strings.HasPrefix(mname, "Json") {
            if mtype.NumIn() != 2 {
                return fmt.Errorf("method %s has wrong number of ins: %d", mname, mtype.NumIn())
            }
            arg := mtype.In(1)
            if arg.String() != "*http.Request" {
                return fmt.Errorf("%s argument type not exported: %s", mname, arg)
            }
            this.methods[mname] = &Method{method, true}
        } else {
            if mtype.NumIn() != 3 {
                return fmt.Errorf("method %s has wrong number of ins: %d", mname, mtype.NumIn())
            }
            reply := mtype.In(1)
            if reply.String() != "http.ResponseWriter" {
                return fmt.Errorf("%s argument type not exported: %s", mname, reply)
            }
            arg := mtype.In(2)
            if arg.String() != "*http.Request" {
                return fmt.Errorf("%s argument type not exported: %s", mname, arg)
            }
            this.methods[mname] = &Method{method, false}
        }
    }
    return nil
}

// ... //

type Hello struct {
}

func (this *Hello) Print(w http.ResponseWriter, r *http.Request) map[string]interface{} {
    w.Write([]byte("print"))
    return nil
}

func (this *Hello) Hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func (this *Hello) JsonHello(r *http.Request) {
}

func main() {

    server := NewServer()
    fmt.Println(server.Register(new(Hello)))
    server.Start(":8080")

}
`