1. 程式人生 > >go隨聊-反射reflect

go隨聊-反射reflect

reflect即反射。對於C++程式設計師來說比較陌生,對於Java或是C#程式設計師來說理解反射就易如反掌了。golang中為我們提供了reflect包用於反射。

package reflect

接下來就要介紹golang中的reflect package了。

reflect包有兩個資料型別,一個是Type,一個是Value。 

1.Type就是定義的型別的一個數據型別

2.Value是值的型別

反射是一種檢查儲存在介面變數中的<值,型別>對的機制,藉助go反射包提供的reflect.TypeOf和reflect.ValueOf可以方便的訪問到一個介面值的reflect.Type和reflect.Value部分,從而可進一步得到這個介面的結構型別和對其進行值的修改操作。

Type

type Type interface {
	// Methods applicable to all types.

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of exported methods in the type's method set.
	NumMethod() int

	// Name returns the type's name within its package.
	// It returns an empty string for unnamed types.
	Name() string

	// PkgPath returns a named type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or unnamed (*T, struct{}, []int),
	// the package path will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	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

	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Ptr: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// 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

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// 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

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any anonymous structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing anonymous fields.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// 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

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// 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
}

部分函式介紹

獲取 t 型別的字串描述。 
func (t *rtype) String() string

獲取 t 型別在其包中定義的名稱,未命名型別則返回空字串。 
func (t *rtype) Name() string

獲取 t 型別所在包的名稱,未命名型別則返回空字串。 
func (t *rtype) PkgPath() string

獲取 t 型別的類別。 
func (t *rtype) Kind() reflect.Kind

switch typ.Kind() {
	case reflect.Ptr:
		fmt.Println("it's ptr")
	case reflect.Int:
		...
}

獲取 t 型別的方法數量。 
func (t *rtype) NumMethod() int

type Demo struct {
	Param string
}
func (this *Demo) Hello() {
	fmt.Println("hello")
}

func Test1(t *testing.T){
	demo:=&Demo{
		Param:"1234",
	}
	typ:= reflect.TypeOf(demo)
	fmt.Println("NumMethod:",typ.NumMethod())
}

 $NumMethod:1

根據索引獲取 t 型別的方法,如果方法不存在,則 panic。 
func (t *rtype) Method(int) reflect.Method

type Demo struct {
	Param string
}
func (this *Demo) Hello() {
	fmt.Println("hello")
}

func Test1(t *testing.T){
	demo:=&Demo{
		Param:"1234",
	}
	typ:= reflect.TypeOf(demo)
	method:=typ.Method(0)
	fmt.Println("方法名:",method.Name)
}

$方法名: Hello

func ValueOf

func ValueOf(i interface{}) Value 

獲取物件型別和值

type Demo struct {
	Param1 string
	Param2 string
}
func (this *Demo) Hello() {
	fmt.Println("hello")
}

func Test1(t *testing.T){
	demo:=&Demo{
		Param1:"1",
		Param2:"2",
	}
	value:= reflect.ValueOf(demo)
	name:= reflect.Indirect(value).Type().Name()
	fmt.Println("值得名字:",name)
	d:=value.Interface().(*Demo)
	fmt.Println(d)
}

$值得名字: Demo
$&{1 2}

通過反射修改物件

方法一:

demo:=&Demo{
		Param1:"1",
		Param2:"2",
	}
value:= reflect.ValueOf(demo)
p:=value.Interface().(*Demo)
p.Param1="1_1"
p.Param2="2_2"
fmt.Println(p)

方法二:

demo:=&Demo{
		Param1:"1",
		Param2:"2",
	}
value:= reflect.ValueOf(demo)
e:=value.Elem()
p:=e.Addr().Interface().(*Demo)
p.Param1="1_1"
p.Param2="2_2"
fmt.Println(p)

反射例子

一般的RPC框架中都會用到反射機制,比如服務提供端使用反射來發布服務,服務消費方呼叫的時候內部也會用到反射。

首先定義服務類Hello

type CmdIn struct {
	Param string
}

func (this *CmdIn) String() string {
	return fmt.Sprintf("[CmdIn Param is %s]",this.Param)
}

type CmdOut struct {
	Result int
	Info string
}

func (this *CmdOut) String() string {
	return fmt.Sprintf("Result[%d] Info[%s]",this.Result,this.Info)
}

//服務提供類Hello
type Hello struct {
	Param string
}

//服務函式Test1
func (this *Hello) Test1(ctx context.Context, in *CmdIn, out *CmdOut) error {
	fmt.Println("接收到服務消費方輸入:",in.Param)
	out.Result=12345678
	out.Info="我已經吃飯了!"
	return nil
}

Hello服務類提供服務函式Test1,其中列印消費端傳來的引數,然後返回資訊給消費端

反射框架類

var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()

type MethodType struct {
	method			reflect.Method
	ArgType    		reflect.Type
	ReplyType  		reflect.Type
}

type Reflect struct {
	Name     string
	Rcvr     reflect.Value
	Typ      reflect.Type
	Method   map[string]*MethodType
}

func (this *Reflect) Setup(rcvr interface{}, name string) {
	this.Typ = reflect.TypeOf(rcvr)
	this.Rcvr = reflect.ValueOf(rcvr)
	this.Name = reflect.Indirect(this.Rcvr).Type().Name()
	this.Method = suitableMethods(this.Typ, true)
}

func (this *Reflect) Call(ctx context.Context,service string,method string,in *CmdIn,out *CmdOut) error {
	mtype := this.Method[method]
	returnValues := mtype.method.Func.Call([]reflect.Value{this.Rcvr, reflect.ValueOf(ctx), reflect.ValueOf(in), reflect.ValueOf(out)})
	errInter := returnValues[0].Interface()
	if errInter != nil {
		return errInter.(error)
	}else{
		return nil
	}
}

func suitableMethods(typ reflect.Type, reportErr bool) map[string]*MethodType {
	methods := make(map[string]*MethodType)
	for m := 0; m < typ.NumMethod(); m++ {
		method := typ.Method(m)
		mtype := method.Type
		mname := method.Name
		if method.PkgPath != "" {
			continue
		}
		if mtype.NumIn() != 4 {
			if reportErr {
				fmt.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
			}
			continue
		}
		ctxType := mtype.In(1)
		if !ctxType.Implements(typeOfContext) {
			if reportErr {
				fmt.Println("method", mname, " must use context.Context as the first parameter")
			}
			continue
		}

		argType := mtype.In(2)
		if !isExportedOrBuiltinType(argType) {
			if reportErr {
				fmt.Println(mname, "parameter type not exported:", argType)
			}
			continue
		}
		// Third arg must be a pointer.
		replyType := mtype.In(3)
		if replyType.Kind() != reflect.Ptr {
			if reportErr {
				fmt.Println("method", mname, "reply type not a pointer:", replyType)
			}
			continue
		}
		// Reply type must be exported.
		if !isExportedOrBuiltinType(replyType) {
			if reportErr {
				fmt.Println("method", mname, "reply type not exported:", replyType)
			}
			continue
		}
		// Method needs one out.
		if mtype.NumOut() != 1 {
			if reportErr {
				fmt.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
			}
			continue
		}
		// The return type of the method must be error.
		if returnType := mtype.Out(0); returnType != typeOfError {
			if reportErr {
				fmt.Println("method", mname, "returns", returnType.String(), "not error")
			}
			continue
		}
		methods[mname] = &MethodType{method: method, ArgType: argType, ReplyType: replyType}
	}
	return methods
}

func isExportedOrBuiltinType(t reflect.Type) bool {
	for t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return isExported(t.Name()) || t.PkgPath() == ""
}

func isExported(name string) bool {
	rune, _ := utf8.DecodeRuneInString(name)
	return unicode.IsUpper(rune)
}

服務提供端釋出服務(服務註冊)

hello:=&Hello{}
reflect:=&Reflect{}
//將hello類註冊到服務框架中
reflect.Setup(hello,"Hello")

服務消費端呼叫

hello:=&Hello{}
reflect:=&Reflect{}
reflect.Setup(hello,"Hello")

fmt.Println("發起消費方呼叫")
//定義輸入引數
in:=&CmdIn{
    Param:"吃飯了嗎?",
}
//定義輸出引數
out:=&CmdOut{}
//消費方訪問服務提供方hello函式Test1
err:=reflect.Call(context.Background(),"Hello","Test1",in,out)
if err!=nil {
    fmt.Println(err)
}else{
    fmt.Println("接收到服務提供方返回:",out)
}

服務消費端並不是直接呼叫hello.Test1函式,而是通過reflect.Call方式呼叫,傳入服務名Hello和需要訪問的函式Test1。