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。