Go(6)--反射

      反射是指计算机程序在运行时(Run time)可以访问、检测和修改本身状态或行为的一种能力。即反射就是程序能够在运行时动态地查看自己的状态,并且允许修改自身的行为

1. 基本概念

      Go 的反射借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构iface,反射基于这个转换后的数据结构来访问和操作实例的值和类型。

// src/runtime/runtime2.go

type iface struct{
	tab *itabl			// itab 存放类型及方法指针信息
	data unsafe.Pointer // 数据信息
}
  • itab:用来存放接口自身类型和绑定的实例类型及实例相关的函数指针。
  • data:指向接口绑定的实例的副本,接口的初始化也是一种值拷贝。

      data 指向具体的实例数据,如果传递给接口的是值类型,则 data 指向的是实例的副本,如果传递给接口的是指针类型,则 data 指向指针的副本。

// src/runtime/runtime2.go

type itab struct{
	inter *interfacetype	// 接口自身的静态类型
	_type *_type			// _type 就是接口存放的具体实例的类型(动态类型)
	hash uint32				// hash 存放具体类型的 Hash 值
	_ [4]byte
	fun [1]uintptr
}
  • inner:是指向接口类型元信息的指针。
  • _type:是指向接口存放的具体类型元信息的指针,iface里面的 data 指针指向的是该类型的值。一个是类型信息,另一个是类型的值。
  • hash:是具体类型的 Hash 值,_type 里面也有 hash,这里冗余存放主要是为了接口断言或类型查询时快速访问。
  • func: 是一个函数指针,指向的是具体类型的方法。

1.1 基本数据结构和入口函数

      rtype是反射包里面用来描述类型公共信息的结构,同时每一种基础类型都封装了一个特定的结构。

type rtype struct {
	size          uintptr
	ptrdata       uintptr
	hash          uint32
	tflag         tflag
	align         uint8
	fieldAlign    uint8
	kind          uint8
	alg           *typeAlg
	gcdata        *byte
	str           nameOff
	ptrToThis     typeOff
}

// 数组类型
type arrayType struct {
	rtype
	elem  *rtype // array element type
	slice *rtype // slice type
	len   uintptr
}

// 指针类型
type ptrType struct {
	rtype
	elem *rtype // pointer element (pointed at) type
}

1.1.1 reflect.Type

      rtype实现了接口reflect.Type,Go 的 reflect 包通过函数reflect.TypeOf()返回一个Type类型的接口,使用者通过接口来获取对象的类型信息。

	func TypeOf(i interface{}) Type

      reflect.Type接口的主要方法如下:

  1. 所有类型通用的方法
	// 返回包含包名的类型名字,对于未命名类型返回的是空
	Name() string

	// 返回该类型的底层基础类型
	Kind() Kind

	// 确定当前类型是否实现了 u 接口类型
	// 注意这里的 u 必须是接口类型的 Type
	Implements(u Type) bool

	// 判断当前类型的实例是否能强制转化为 u 类型变量
	AssignableTo(u Type) bool

	// 判断当前类型的实例是否能强制类型转化为 u 类型变量
	ConvertibleTo(u Type) bool

	// 判断当前类型是否支持比较(等于或不等于)
	// 支持等于的类型可以作为 map 的 key
	Comparable() bool

	// 返回一个类型的方法的个数
	NumMethod() int

	// 通过索引值访问方法,索引值必须属于[0, NumMethod()],否则引发 panic
	Method(int) Method

	// 通过方法名获取方法
	MethodByName(string) (Method, bool)

	// 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
	PkgPath() string

	// 返回存放该路径的实例需要多大的字节空间
	Size() uintptr
  1. 不同基础类型的专有方法

      这些方法是某种类型特有的,如果不是某种特定类型却调用了该类型的方法,则会引发panic,所以为了避免panic,在调用特定类型的专有方法前,需知道其类型,如不确定类型,需调用Kind()方法确定类型后再调用类型的专有方法。

	// Int*, Unit*, Float*, Complex*: Bits
	// Array: Element, Len
	// Chan: ChanDir, Elem
	// Func: In, NumIn, Out, NumOut, IsVariadic
	// Map: Key, Elem
	// Ptr: Elem
	// Slice: Elem
	// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// 返回类型的元素类型,该方法只适用于 Array、Chan、Map、Ptr、Slice 类型
	Elem()
	
	// 返回数值类型内存占用的位数
	Bits() int

	// struct 类型专用的方法
	// 返回字段数目
	NumField() int
	// 通过整数索引获取 struct 字段
	Field(i int) StructField
	// 获取嵌入字段获取 struct 字段
	FieldByIndex(index []int) StructField
	// 通过名字查找获取 struct 字段
	FieldByName(name string)(StructField, bool// func 类型专用的方法
	// 函数是否是不定参数函数
	IsVariadic() bool
	// 输入参数个数
	NumIn() int
	// 返回值个数
	NumOut() int
	// 返回第 i 个输入参数类型
	In(i int) Type
	// 返回第 i 个返回值类型
	Out(i int) Type

	// map 类型专用的方法
	// 返回 map key 的 type
	Key() Type
package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string "学生姓名"
	Age int `a:"111"b:"3333"`
}

func main() {
	s := Student{}
	rt := reflect.TypeOf(s)
	fieldName, ok := rt.FieldByName("Name")

	// 取 tag 数据
	if ok{
		fmt.Println("tag = ", fieldName.Tag)				// tag =  学生姓名
	}

	fieldAge, ok := rt.FieldByName("Age")
	// 可以像 JSON 一样,取 tag 里的数据,多个 tag 之间无逗号,tag 不需要引号
	if ok{
		fmt.Println("tag = ", fieldAge.Tag.Get("a"))	// tag =  111
		fmt.Println("tag = ", fieldAge.Tag.Get("b"))	// tag =  3333
	}

	fmt.Println("type_Name = ", rt.Name())					// type_Name =  Student
	fmt.Println("type_NumField = ", rt.NumField())			// type_NumField =  2
	fmt.Println("type_PkgPath = ", rt.PkgPath())			// type_PkgPath =  main
	fmt.Println("type_String = ", rt.String())				// type_String =  main.Student

	fmt.Println("type.Kind.String = ", rt.Kind().String())	// type.Kind.String =  struct
	fmt.Println("type.String() = ", rt.String())			// type.String() =  main.Student

	// 获取结构类型的字段名称
	for i := 0; i < rt.NumField(); i++{
		fmt.Printf("type.Field[%d].Name := %v \n", i, rt.Field(i).Name)
		// type.Field[0].Name := Name
		// type.Field[1].Name := Age
	}

	sc := make([]int, 10)
	sc = append(sc, 1, 2, 3)
	sct := reflect.TypeOf(sc)

	// 获取 slice  元素的 Type
	scet := sct.Elem()

	fmt.Println("slice element type.Kind() = ", scet.Kind()) // slice element type.Kind() =  int
	fmt.Printf("slice element type.Kind() = %d \n", scet.Kind()) // slice element type.Kind() =  2
	fmt.Println("slice element type.String() = ", scet.String()) // slice element  type.String() =  int

	fmt.Println("slice element type.Name() = ", scet.Name())	// slice element type.Name() =  int
	fmt.Println("slice type.NumMethod() = ", scet.NumMethod())	// slice type.NumMethod() =  0
	fmt.Println("slice type.PkgPath() = ", scet.PkgPath())		// slice type.PkgPath() =
	fmt.Println("slice type.PkgPath() = ", sct.PkgPath())		// slice type.PkgPath() =
}

      对于reflect.TypeOf(a),传进去的实参a有两种类型,一种是接口变量,另一种是具体类型变量。如果a是具体类型变量,则返回的是具体的类型信息。如果a是一个接口变量,则函数的返回值分为两种情况。

      如果a绑定了具体类型实例,则返回的是接口的动态类型。

      如果a没有绑定具体类型实例,则返回的是接口自身的静态类型信息。

package main

import (
	"reflect"
)

type INT int

type A struct {
	a int
}

type B struct {
	b string
}

type Ita interface {
	String() string
}

func (b B)String() string{
	return b.b
}

func main() {
	var a INT = 12
	var b int = 14

	// 实参是具体类型,reflect.Type 返回的是其静态类型
	ta := reflect.TypeOf(a)
	tb := reflect.TypeOf(b)
	// INT 和 int 是两个类型,两者不相等
	if ta == tb{
		println("ta == tb")
	}else{
		println("ta != tb")
	}

	println(ta.Name()) // INT
	println(tb.Name()) // int

	// 底层基础类型
	println(ta.Kind().String()) // int
	println(tb.Kind().String()) // int


	s1 := A{1}
	s2 := B{"tata"}

	// 实参是具体类型,reflect.TypeOf 返回的是其静态类型
	println(reflect.TypeOf(s1).Name())	// A
	println(reflect.TypeOf(s2).Name())	// B

	// Type 的 Kind() 方法返回的是基础类型,类型 A 和 B 的底层基础类型都是 struct
	println(reflect.TypeOf(s1).Kind().String())	// struct
	println(reflect.TypeOf(s2).Kind().String())	// struct

	ita := new(Ita)
	var itb Ita = s2

	// 实参未绑定具体变量的接口类型,reflect.TypeOf 返回的是接口类型本身
	// 也是接口的静态类型
	println(reflect.TypeOf(ita).Elem().Name())	// Ita
	println(reflect.TypeOf(ita).Elem().Kind().String()) // interface

	// 实参绑定具体变量的接口类型,reflect.TypeOf 返回的是所绑定的具体类信息
	// 也是接口的动态类型
	println(reflect.TypeOf(itb).Name())	// B
	println(reflect.TypeOf(itb).Kind().String()) // struct
}

1.1.2 reflect.Value

      reflect.Value表示实例的值信息,reflect.Value是一个struct,并提供了一系列的method给使用者。

type Value struct {
	
	typ *rtype
	
	ptr unsafe.Pointer

	flag
}

      reflect.Value总共有三个字段,一个是值得类型指针typ,另一个是指向值的指针ptr,最后一个是标记字段flag

      通过reflect.ValueOf()函数获取实例的值信息。

func ValueOf(i interface{}) Type
package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id int
	Name string
	Age int
}

func (this User)String()  {
	println("User: ",this.Id, this.Name, this.Age)
}

func Info(o interface{})  {
	// 获取 Value 信息
	v := reflect.ValueOf(o)

	// 通过 value 获取 Type
	t := v.Type()

	// 类型名称
	println("Type: ", t.Name())

	// 访问接口字段名、字段类型和字段值信息
	println("Fields:")
	for i := 0; i < t.NumField(); i++{
		field := t.Field(i)
		value := v.Field(i).Interface()
		// 类型查询
		switch value := value.(type) {
			case int:
				fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
			case string:
				fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
			default:
				fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
		}
	}
}

func main() {
	u := User{1, "Tom", 30}
	Info(u)
}

在这里插入图片描述

1.2 基础类型

      Type接口有一个Kind()方法,返回的是一个整数枚举值,不同的值代表不同的类型。

      Go 总共定义了 26 种基础类型,具体如下:

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsagePointer
)

      底层类型和基础类型

      底层类型和基础类型的区别在于,基础类型是抽象的类型划分,底层类型是针对每一个具体的类型来定义的,比如不同的 struct类型再基础类型上都划归为struct类型,但不同的struct底层类型是不一样的。

type A struct{
	a int
}

type Aa A

type B struct{
	b int
}

      A、Aa、B 的基础类型都是struct,B 的底层类型是其自身,A 和 Aa 的底层类型都是 A。

2. 反射规则

      反射对象 ValueType 和类型实例之间可以相互转化,转化关系图如下:

在这里插入图片描述

2.1 反射 API

  1. 从实例到 Value

            通过实例获取 Value 对象,直接使用reflect.ValueOf()函数。

	func ValueOf(i interface{}) Value
  1. 从实例到 Type

            通过实例获取 Type 对象,直接使用reflect.TypeOf()函数。

	func TypeOf(i interface{}) Value
  1. 从 Type 到 Value

            Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflce 包提供了两种方法。

	// New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型
	func New(typ Type) Value

	// Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址,值不可以改变
	func Zero(typ Type) Value

            如果知道一个类型值得底层存放地址,则可以通过下面得函数依据 type 和该地址值恢复出 Valueu

	func NewAt(typ Type, p unsafe.Pointer) Value
  1. 从 Value 到 Type
                从反射对象 Value 到 Type 可以直接调用 Value 得方法,因为 Value 内部存放着到 Type 类型得指针。
	func (v Value) Type() Type
  1. 从 Value 到实例

            Value 本身就包含类型和值信息。

	// 该方法嘴通用,用来将 Value 转化为空接口,该空接口内部粗放具体类型实例
	// 可以使用接口类型查询取还原为具体得类型
	func (v Value) Interface() {i interface{}}

	// Value 自身也提供丰富得方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
	func (v Value) Bool() bool
	func (v Value) Float() float64
	func (v Value) Int() int64
	func (v Value) Uint uint64
  1. 从 Value 的指针到值

            从一个指针类型的 Value 获得值类型 Value 有两种方法:

	// 如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如果 v 类型是指针,则返回指针值得 Value,否则引起 panic
	func (v Value) Elem() Value

	// 如果 v 是指针,则返回指针值得 Value,否则返回 v 自身,该函数不会引起 panic
	func Indirect(v Value) Value
  1. Type 指针和值的相互转化

            (1)指针类型 Type 到值类型 Type。

	// t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic
	// Elem 返回得是其内部元素得 Type
	t.Elem() Type

            (2)值类型 Type 到指针类型 Type。

	// PtrTo 返回的是指向 t 的指针类型 Type
	func PtrTo(t Type) Type
  1. Value 值得可修改性

            Value 值得修改设计如下两个方法:

	// 通过 CanSet 判断是否能修改
	func (v Value) CanSet() bool

	// 通过 Set 进行修改
	func (v Value) Set(x Value)

      如果调用反射的方法reflect.ValueOf()传进去的是一个值类型变量,则获得的Value实际上是元对象的一个副本,这个Value是无论如何也不能修改的。

      如果传进去的是一个指针,虽然接口内部转换的也是指针的副本,但通过指针还是可以访问到最原始的对象,此时可以修改。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id int
	Name string
	Age int
}

func main() {
	u := User{1, "wangzhao", 21}

	va := reflect.ValueOf(u)
	vb := reflect.ValueOf(&u)

	// 值类型是不可修改的
	fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet()) // false false

	// 指针类型是不可修改的
	fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet()) // false true

	fmt.Printf("%v\n", vb)

	name := "wangshuai"
	vc := reflect.ValueOf(name)	// &{1 wangzhao 21}

	// 通过 Set 函数修改变量的值
	vb.Elem().FieldByName("Name").Set(vc)

	fmt.Printf("%v", vb)	// &{1 wangshuai 21}
}

2.2 反射三定律

  1. 反射可以从接口值得到反射对象
  2. 反射可以从接口对象得到反射值
  3. 若要修改一个反射对象,则其值必须可以修改

3. inject 库

      inject借助反射提供了对 2 种类型实体的注入:函数和结构。

3.1 依赖注入和控制反转

      很多编程语言的框架中都使用到了依赖注入和控制反转,如 Java 中的 Spring,Go 的 martini。

      控制反转对象的生命周期不再由程序员直接控制,而是转交给框架去负责

      依赖注入通过注入参数或实例的方式实现控制反转,“依赖注入”是实现“控制反转”的一种方式。

3.2 inject 实现

      inject是 Go 语言依赖注入的实现,它实现了对结构(struct)和函数的依赖注入。

      如何通过一个字符串类型的函数名调用函数。Go 中没有 Java 中的 Class.forName 方法可以通过类名直接构造对象,所以这种方法行不同。可以通过 map 实现一个字符串到函数的映射。

package main

func f1()  {
	println("f1")
}

func f2()  {
	println("f2")
}


func main() {
	funcs := make(map[string]func())
	funcs["f1"] = f1
	funcs["f2"] = f2
	
	funcs["f1"]()
	funcs["f2"]()
}

      该方法的缺陷是,mapValue被写成func(),不同参数和返回值类型的函数并不能通用。如果将 Value 定义为 interface{}空接口类型,可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。

      inject包借助反射实现函数的注入调用。

package main

import (
	"fmt"
	"github.com/codegangsta/inject"
)

type S1 interface {
}

type S2 interface {
}

func Format(name string, company S1, level S2, age int)  {
	fmt.Printf("name=%s, company=%s, level=%s, age=%d!\n", name, company, level, age)
}

func main() {
	// 控制实例的创建
	inj := inject.New()

	fmt.Println(inj) // &{map[] <nil>}

	// 实参注入
	inj.Map("tom")
	inj.MapTo("tencent", (*S1)(nil))
	inj.MapTo("T4", (*S2)(nil))
	inj.Map(23)
	
	// 函数反转调用
	inj.Invoke(Format)
}

在这里插入图片描述

      inject.New()相当于创建了一个控制实例,尤其来实现对函数的注入调用。inject包不但提供了对函数的注入,还实现了对struct类型的注入。

package main

import (
	"fmt"
	"github.com/codegangsta/inject"
)

type S1 interface {
}

type S2 interface {
}

type Staff struct {
	Name string `inject`
	Company S1  `inject`
	Level S2	`inject`
	Age int	    `inject`
}

func main() {
	// 创建被注入实例
	s := Staff{}

	// 控制实例的创建
	inj := inject.New()

	// 初始化注入值
	inj.Map("tom")
	inj.MapTo("tencent", (*S1)(nil))
	inj.MapTo("T4", (*S2)(nil))
	inj.Map(23)

	// 实现对 struct 注入
	inj.Apply(&s)

	fmt.Println("s = ", s)

}

在这里插入图片描述

      可以看到inject提供了一种对结构类型的通用注入方法。

3.3 inject 原理分析

  1. 入口函数 New

      inject.New()函数构建一个具体类型injector实例作为内部注入引擎,返回得是一个Inject类型得接口。

func New() Injector{
	return &injector{
		values: make(map[reflect.Type]reflect.Value),
	}
}
type Injector interface{

	// 抽象生成注入结构实例得接口
	Applicator
	
	// 抽象函数调用的接口
	Invoker
	
	// 抽象注入参数的接口
	TypeMapper
	
	// 实现一个注入实例链,下游的能覆盖上游的类型
	SetParent(Injector) 
}

      TypeMapper接口实现对注入参数操作的汇总,包括设置和查找相关的类型和值的方法。注意:无论函数的实参,还是结构的字段,在inject内部,都存放在map[reflect.Type]reflect.Value类型的map里面。

type TypeMapper interface{
	// 如下三个方式是设置参数
	Map(interface{}) TypeMapper
	MapTo(interface{}, interface{}) TypeMapper
	Set(reflect.Type, reflect.Value) TypeMapper

	// 查找参数
	Get(reflect.Type) reflect.Value
}

      Invoker接口中Invoke方法是对被注入实参数函数的调用:

type Invoker interface{
	Invoke(interface{}) ([]reflect.Value, error)
}

      Applicator接口中Apply方法实现对结构的注入:

type Applicator interface{
	Apply(interface{}) error
}

      inject包的处理流程:

  1. 通过inject.New()创建注入引擎,注入引擎被隐藏,返回的是Injector接口类型变量
  2. 通过TypeMapper接口(Injector内嵌TypeMapper)的方法注入struct的字段值或函数的实参值
  3. 调用Invoker方法执行被注入的函数,或者调用Applicator接口方法获得被注入后的结构实例

      内部实现

type injector struct{
	values map[reflect.Type] reflect.Value
	parent Injector
}

      values里面存放的可以是被注入struct的字段类型和值,也可以是函数实参的类型和值。注意:values是以reflect.TypeKeymap,如果一个结构的字段类型相同,则后面注入的参数会覆盖去前面的参数。规避办法是使用MapTo方法,通过抽象出一个接口类型来避免被覆盖。

func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
	i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
	return i
}

      injector里面的parent的作用是实现多个注入引擎,构成一个链。

func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
	// 构造函数类型的 Type
	t := reflect.TypeOf(f)

	// 构造一个存放函数实参 Value 值得数组
	var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func

	// 使用反射获取函数实参 reflect.Type ,逐个去 injector 中查找注入得 Value 值
	for i := 0; i < t.NumIn(); i++ {
		argType := t.In(i)
		val := inj.Get(argType)
		if !val.IsValid() {
			return nil, fmt.Errorf("Value not found for type %v", argType)
		}

		in[i] = val
	}

	// 反射调用函数
	return reflect.ValueOf(f).Call(in), nil
}

4. 反射的优缺点

4.1 优点

  1. 通用性

            一些类库和框架代码需要一种通用的处理模式,而不是针对每一种场景做硬编码处理。

  1. 灵活性

            反射提供了一种程序了解字节和改变自己的能力,方便测试工具的开发。

4.2 缺点

  1. 反射是脆弱的

            由于反射可以在程序运行时修改程序的状态,这种修改没有经过编译器的严格检查,不正确的修改很容易导致程序的崩溃。

  1. 反射是晦涩难懂的

            语言的反射接口由于涉及语言运行时,没有具体的类型系统的约束,接口的抽象级别高但实现细节复杂,导致使用反射的代码难以理解。

  1. 反射有部分性能损失

            反射提供动态修改程序状态的能力,必然不是直接的地址引用,而是借助运行时构造一个抽象层,这种间接访问会有性能的缺失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值