Go语言——反射 下

简介: Go语言——反射 下

2. reflect.ValueOf()和reflect.Value类型

通过反射获取值信息

当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。

reflect.Value 类型有很多方法(https://golang.google.cn/pkg/reflect/)。我们可以调用这些方法来观察和操纵一个 reflect.Value 属主值表示的 值。这些方法中的有些适用于所有种类类型的值,有些只适用于一种或几种类型的值。

通过不合适的 reflect.Value 属主值调用某个方法将在运行时产生一个painc。

请阅读 reflect 代码库 中各个方法的文档来获取如何正确地使用这些方法。

一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被赋值)。

如果一个 Go 值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go 值。

注意:reflect.ValueOf(&v) 函数直接返回的 reflect.Value 值都是不可修改的。

.Elem() 函数后返回的(虽然也是reflect.Value类型)才可以修改。

反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。

Go语言中使用 reflect.Value 获取和设置变量的值。

2.1 使用反射值对象<包装>任意值

Go语言中,使用 reflect.ValueOf()函数获得反射值对象(reflect.Value)。

书写格式如下:

valueOfR := reflect.ValueOf(rawValue)

reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。

reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。

2.2 从反射值对象<获取>被包装的值

Go语言中可以通过 reflect.Value 重新获得原始值。

2.2.1 从反射值对象(reflect.Value)中获取值的方法

可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。

image.png

2.2.2 从反射值对象(reflect.Value)中获取值的例子

下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象(reflect.Value)。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。

func main() {
  // 声明整型变量a并赋初值
  var a int = 1024
  // 获取指针变量&a的反射值对象,并取元素
  valueOfA := reflect.ValueOf(&a).Elem()
  // 方法1. 获取interface{}类型的值, 通过类型断言转换
  var getA int = valueOfA.Interface().(int)
  // 方法2. 获取64位的值, 强制类型转换为int类型
  var getA2 int = int(valueOfA.Int())
  fmt.Printf("%T\t%T\n",getA, getA2)
}

代码输出如下:

int int
• 1

2.3 通过反射值对象访问结构体成员的值

image.png

简单使用:

// 定义结构体
type dummy struct {
  a int
  b string
  float32
  bool
  next *dummy // 嵌入字段
}
func main() {
  dum := dummy{
    next: &dummy{},
  }
  // 值包装结构体
  valueOfDum := reflect.ValueOf(&dum).Elem()
  // 获取字段数量
  fmt.Println("NumField(): ", valueOfDum.NumField())
  // 获取索引为2的字段(float32字段 匿名字段)
  floatField := valueOfDum.Field(2)
  // 输出字段类型
  fmt.Println("Field(2): ", floatField.Type())
  // 根据名字查找字段
  fmt.Println("FieldByName(\"b\").Type(): ", valueOfDum.FieldByName("b").Type())
  // 根据索引查找值中, next字段的int字段的值
  fmt.Println("FieldByIndex([]int{4}).Type(): ", valueOfDum.FieldByIndex([]int{4}).Type())
  fmt.Println("FieldByIndex([]int{4, 1}).Type(): ", valueOfDum.FieldByIndex([]int{4,1}).Type())
}

运行结果:

NumField():  5
Field(2):  float32
FieldByName("b").Type():  string
FieldByIndex([]int{4}).Type():  *main.dummy
FieldByIndex([]int{4, 0}).Type():  string

例二:

type student struct {
  Name string
  Age int
}
func main() {
  stu:=student{
    Name: "Hyy",
    Age: 19,
  }
  ValueOfStu := reflect.ValueOf(&stu).Elem()
  ValueOfStu.FieldByName("Name").SetString("胡宇洋")
  ValueOfStu.FieldByName("Age").SetInt(12)
  fmt.Println(stu)
}

运行结果:

{胡宇洋 12}

2.4 判断反射值对象的空和有效性

2.4.1 reflect.IsNil()和reflect.IsValid()

反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如下表所示。

image.png

下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。

同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定,参考下面的代码。

反射值对象的零值和有效性判断:

func main() {
  // *int的空指针
  var a *int
  fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
  // nil值
  fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
  // *int类型的空指针
  fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
  // 实例化一个结构体
  s := struct{}{}
  // 尝试从结构体中查找一个不存在的字段
  fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
  // 尝试从结构体中查找一个不存在的方法
  fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
  // 实例化一个map
  m := map[int]int{}
  // 尝试从map中查找一个不存在的键
  fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

注意:

IsValid():对于任何值都调用,判断值的有效性。

IsNil():仅对于接口、map、指针、切片、通道、函数类型可用,其他类型调用 ,panic

2.5通过反射值对象修改变量的值

一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

对于 reflect.Values 也有类似的区别。

有一些 reflect.Values 是可取地址的;其它一些则不可以。

考虑以下的声明语句:

x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := reflect.ValueOf(&x).Elem() // 2 int yes (x)

其中 a 对应的变量则不可取地址,因为 a 中的值仅仅是整数 2 的拷贝副本。

b 中的值也同样不可取地址。

c 中的值还是不可取地址,它只是一个指针 &x 的拷贝。

实际上,所有通过 reflect.ValueOf(x) 直接返回的 reflect.Value 都是不可取地址的。

但是对于 d,它是 c 的解引用方式生成的,指向另一个变量,因此是可取地址的。

我们可以通过调用 reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的 Value。

我们可以通过调用 reflect.Value 的 CanAddr()方法来判断其是否可以被取地址:

fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"

每当我们通过指针间接地获取的 reflect.Value 都是可取地址的,即使开始的是一个不可取地址的 Value。

在反射机制中,所有关于是否支持取地址的规则都是类似的。

func main() {
  y:=[]int{1,2,3}
  e:= reflect.ValueOf(y)
  fmt.Println(e.CanAddr()) //false
  fmt.Println(e.Index(1).CanAddr()) // true
}

例如,slice 的索引表达式 e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。

以此类推,reflect.ValueOf(e).Index(i) 对于的值也是可取地址的,即使原始的 reflect.ValueOf(e) 不支持也没有关系。

使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。

如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。

判定及获取元素的相关方法

使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

image.png

反射值对象的值修改相关方法

使用 reflect.Value 修改值的相关方法如下表所示。

image.png

以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。

在已知值的类型时,应尽量使用值对应类型的反射设置值。

值可修改条件之一:可被寻址

通过反射修改变量值的前提条件之一:这个值必须可以被寻址。

简单地说就是这个变量必须能被修改。

示例代码如下:

func main() {
    var a int = 1024
    valueOfA := reflect.ValueOf(a)
    // 尝试将a修改为1(此处会发生崩溃)
    valueOfA.SetInt(1)
}

程序运行崩溃,打印错误:

panic: reflect: reflect.Value.SetInt using unaddressable value

报错意思是:SetInt 正在使用一个不能被寻址的值。从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下,重新运行:

func main() {
  var a int = 1024
  valueOfA := reflect.ValueOf(&a).Elem()
  valueOfA.SetInt(1)
}

注意:

当 reflect.Value 不可寻址时,使用 Addr()方法也是无法取到值的地址的,同时会发生宕机。

虽然说 reflect.Value 的Addr() 方法类似于语言层的&操作;Elem()方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。

值可修改条件之一:被导出

结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:

type student struct {
  name string
  Age  int
}
func main() {
  stu := student{"hyy", 19}
  valueOfStu := reflect.ValueOf(&stu).Elem()
  valueOfStuAge:=valueOfStu.FieldByName("Age")
  valueOfStuAge.SetInt(20)
  fmt.Println(stu)
  valueOfStuName:=valueOfStu.FieldByName("name")
  valueOfStuName.SetString("hyy2")
}

运行结果:

{hyy 20}
panic: reflect: reflect.Value.SetString using value obtained using unexported field

报错的意思是:SetString() 使用的值来自于一个未导出的字段。

反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 反射值对象 调用该函数。

使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。

将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

func add(a int, b int) int {
  return a+b
}
func main() {
  funcValue:=reflect.ValueOf(add)
  realParameter:= []reflect.Value{reflect.ValueOf(10),reflect.ValueOf(12)}
  resList:=funcValue.Call(realParameter)
  fmt.Println(resList[0].Int())
}

提示

反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。

调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。

因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。

inject库:依赖注入

在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。

正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。

所谓的控制反转就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。

依赖注入是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的一种实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。

控制反转的价值在于解耦,有了控制反转就不需要将代码写死,可以让控制反转的的框架代码读取配置,动态的构建对象,这一点在 Java 的 Spring 框架中体现的尤为突出。

inject 实践

inject 是依赖注入的Go语言实现,它能在运行时注入参数,调用方法,是 Martini 框架(Go语言中著名的 Web 框架)的基础核心。

在介绍具体实现之前,先来想一个问题,如何通过一个字符串类型的函数名来调用函数?

Go语言没有 Java 中的 Class.forName 方法可以通过类名直接构造对象,所以这种方法是行不通的,能想到的方法就是使用 map 实现一个字符串到函数的映射,示例代码如下:

func fl() {
    println ("fl")
}
func f2 () {
    println ("f2")
}
funcs := make(map[string] func ())
funcs ["fl"] = fl
funcs ["f2"] = fl
funcs ["fl"]()
funcs ["f2"]()

但是这有个缺陷,就是 map 的 Value 类型被写成 func(),不同参数和返回值的类型的函数并不能通用。

将 map 的 Value 定义为 interface{} 空接口类型即可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。

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()
  // 实参注入
  // 基于调用reflect.TypeOf得到的类型映射interface{}的值。
  inj.Map("tom")
  // 基于提供的接口的指针,映射interface{}的值。
    // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
  inj.MapTo("tencent", (*S1)(nil))
  inj.MapTo("T4", (*S2)(nil))
  inj.Map(23)
  // 函数反转调用
    // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
    // 它将返回reflect.Value的切片,其中存放原函数的返回值。
    // 如果注入失败则返回error.
  inj.Invoke(Format)
}

运行结果:

name = tom, company=tencent, level=T4, age = 23!

可见 inject 提供了一种注入参数调用函数的通用功能,inject.New() 相当于创建了一个控制实例,由其来实现对函数的注入调用。

inject 包不但提供了对函数的注入,还实现了对 struct 类型的注入,示例代码如下所示:

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 注入
   // 在Type map中维持对结构体中每个域的引用,并用\'inject\'来标记
   // 如果注入失败将会返回一个error.
   inj.Apply(&s)
   //打印结果
   fmt.Printf("s = %v\n", s)
}

运行结果:

s = {tom tencent T4 23}

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

至此,我们仅仅从宏观层面了解 iniect 能做什么,下面从源码实现角度来分析 inject。

inject 原理分析

概述

inject 包中只有 2 个文件,一个是 inject.go 文件和一个 inject_test.go 文件,这里我们只需要关注 inject.go 文件即可。

inject.go 短小精悍,包括注释和空行在内才 157 行代码,代码中定义了 4 个接口,包括一个父接口和三个子接口,如下所示:

type Injector interface {
    Applicator
    Invoker
    TypeMapper
    SetParent(Injector)
}
type Applicator interface {
    Apply(interface{}) error
}
type Invoker interface {
    Invoke(interface{}) ([]reflect.Value, error)
}
type TypeMapper interface {
    Map(interface{}) TypeMapper
    MapTo(interface{}, interface{}) TypeMapper
    Set(reflect.Type, reflect.Value) TypeMapper
    Get(reflect.Type) reflect.Value
}

Injector 接口是 Applicator、Invoker、TypeMapper 接口的父接口,所以实现了 Injector 接口的类型,也必然实现了 Applicator、Invoker 和 TypeMapper 接口:

Applicator 接口只规定了 Apply 成员,它用于注入 struct。

Invoker 接口只规定了 Invoke 成员,它用于执行被调用者。

TypeMapper 接口规定了三个成员,Map 和 MapTo 都用于注入参数,但它们有不同的用法,Get 用于调用时获取被注入的参数。

另外 Injector 还规定了 SetParent 行为,它用于设置父 Injector,其实它相当于查找继承。也即通过 Get 方法在获取被注入参数时会一直追溯到 parent,这是个递归过程,直到查找到参数或为 nil 终止。

injector,InterfaceOf(),New()

type injector struct {
    values map[reflect.Type]reflect.Value
    parent Injector
}
func InterfaceOf(value interface{}) reflect.Type {
    t := reflect.TypeOf(value)
    for t.Kind() == reflect.Ptr {
        t = t.Elem()
    }
    if t.Kind() != reflect.Interface {
        panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
    }
    return t
}
func New() Injector {
    return &injector{
        values: make(map[reflect.Type]reflect.Value),
    }
}

injector 是 inject 包中唯一定义的 struct,所有的操作都是基于 injector struct 来进行的,它有两个成员 values 和 parent。

values 用于保存注入的参数,是一个用 reflect.Type 当键、reflect.Value 为值的 map,理解这点将有助于理解 Map 和 MapTo。

New 方法用于初始化 injector struct,并返回一个指向 injector struct 的指针,但是这个返回值被 Injector 接口包装了。

InterfaceOf 方法虽然只有几句实现代码,但它是 Injector 的核心。

InterfaceOf 方法的参数必须是一个接口类型的指针,如果不是则引发 panic。

InterfaceOf 方法的返回类型是 reflect.Type,大家应该还记得 injector 的成员 values 就是一个 reflect.Type 类型当键的 map。

这个方法的作用其实只是获取参数的类型,而不关心它的值。

示例:

type SpecialString interface{}
func main() {
  fmt.Println(inject.InterfaceOf((*interface{})(nil)))
  fmt.Println(inject.InterfaceOf((*SpecialString)(nil)))
}

运行结果:

interface {}
main.SpecialString

InterfaceOf 方法就是用来得到参数类型,而不关心它具体存储的是什么值。

Map(),MapTo(),Get(),SetParent()

func (i *injector) Map(val interface{}) TypeMapper {
    i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
    return i
}
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
    i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
    return i
}
func (i *injector) Get(t reflect.Type) reflect.Value {
    val := i.values[t]
    if !val.IsValid() && i.parent != nil {
        val = i.parent.Get(t)
    }
    return val
}
func (i *injector) SetParent(parent Injector) {
    i.parent = parent
}

Map 和 MapTo 方法都用于注入参数,保存于 injector 的成员 values 中。

这两个方法的功能完全相同,唯一的区别就是 Map 方法用参数值本身的类型当键,而 MapTo 方法有一个额外的参数可以指定特定的类型当键。但是 MapTo 方法的第二个参数 ifacePtr 必须是接口指针类型,因为最终 ifacePtr 会作为 InterfaceOf 方法的参数。

为什么需要有 MapTo 方法?

因为注入的参数是存储在一个以类型为键的 map 中,可想而知,当一个函数中有一个以上的参数的类型是一样时,后执行 Map 进行注入的参数将会覆盖前一个通过 Map 注入的参数。

SetParent 方法用于给某个 Injector 指定父 Injector。

Get 方法通过 reflect.Type 从 injector 的 values 成员中取出对应的值,它可能会检查是否设置了 parent,直到找到或返回无效的值,最后 Get 方法的返回值会经过 IsValid 方法的校验。

示例代码如下所示:

type SpecialString interface{}
func main() {
  inj := inject.New()
  inj.Map("傻子") // map[string]="傻子"
  inj.MapTo("你呀", (*SpecialString)(nil)) // map[SpecialString]="你呀"
  inj.Map(20) // map[int]=20
  // 相当于查找键为string的map键值对
  fmt.Println("字符串是否有效?", inj.Get(reflect.TypeOf("hyy")).IsValid())
  // 查找map[SpecialString]
  fmt.Println("特殊字符串是否有效?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid())
  // map[int]
  fmt.Println("int 是否有效?", inj.Get(reflect.TypeOf(18)).IsValid())
  // map[[]byte]
  fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
  inj2 := inject.New()
  // map2[[]byte]=[]byte("test")
  inj2.Map([]byte("test"))
  // 设置到父inject中,如果在子中查不到,自动去父中查,父中再找不到,error
  inj.SetParent(inj2)
  fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid())
    // 从map中取元素
    fmt.Println(string(inj.Get(reflect.TypeOf([]byte("12"))).Bytes())) // test
}

运行结果:

字符串是否有效? true
特殊字符串是否有效? true
int 是否有效? true
[]byte 是否有效? false
[]byte 是否有效? true
test

通过以上例子应该知道 SetParent 是什么样的行为,是不是很像面向对象中的查找链?

Invoke()

func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
    // 获取参数f的反射类型对象
    t := reflect.TypeOf(f)
    // 准备实参,根据参数 函数f 需要的参数数量创建切片
    var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
    // 遍历获取所需参数类型,根据类型去values中获取对应键值对,获取不到就报错,获取到就并入实参切片。
    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
    }
    // 根据实参切片作为参数调用函数f
    return reflect.ValueOf(f).Call(in), nil
}

Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。

NumIn()

NumIn 返回一个函数类型的输入参数计数。如果类型的 Kind 不是 Func,它会恐慌。

In()

In 返回函数类型的第 i 个输入参数的类型。

如果类型的 Kind 不是 Func,它会恐慌。

如果 i 不在 [0, NumIn()) 范围内,它会恐慌。

Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。

例子:

type SpecialString interface{}
func Say(name string, gender SpecialString, age int) {
  fmt.Printf("My name is %s, gender is %s, age is %d!\n", name, gender, age)
}
func main() {
  inj := inject.New()
  // map[string]="张三"
  inj.Map("张三")
  // map[SpecialString]="男"
  inj.MapTo("男", (*SpecialString)(nil))
  inj2 := inject.New()
  // map2[int]=25
  inj2.Map(25)
  inj.SetParent(inj2)
  inj.Invoke(Say)
}

运行结果:

My name is 张三, gender is 男, age is 25!

上面的例子如果没有定义 SpecialString 接口作为 gender 参数的类型,而把 name 和 gender 都定义为 string 类型,那么 gender 会覆盖 name 的值。

Apply()

func (inj *injector) Apply(val interface{}) error {
    // 获取参数的反射值对象
    v := reflect.ValueOf(val)
    // 如果你传入的参数的种类是指针类型,就取元素
    for v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    // 如果传入的参数的种类不是结构体,返回nil
    if v.Kind() != reflect.Struct {
        return nil
    }
    // 获取反射类型对象
    t := v.Type()
    // 遍历字段域
    for i := 0; i < v.NumField(); i++ {
        // 选择 反射值对象字段域
        f := v.Field(i)
        // 选择 反射类型对象字段域
        structField := t.Field(i)
        // 判断当前反射值对象是否可修改,且当前反射类型对象的标签是否为'inject'
        if f.CanSet() && structField.Tag == "inject" {
            // 获取当前反射值对象的反射类型对象
            ft := f.Type()
            // 从values中获取以ft为键的值
            v := inj.Get(ft)
            if !v.IsValid() {
                return fmt.Errorf("Value not found for type %v", ft)
            }
            f.Set(v)
        }
    }
    return nil
}

Apply 方法是用于对 struct 的字段进行注入,参数为指向底层类型为结构体的指针。

可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),并且此字段的 tag 设置为inject。

实例:

type SpecialString interface{}
type TestStruct struct {
    Name   string `inject`
    Nick   []byte
    Gender SpecialString `inject`
    uid    int           `inject`
    Age    int           `inject`
}
func main() {
    s := TestStruct{}
    inj := inject.New()
    inj.Map("张三")
    inj.MapTo("男", (*SpecialString)(nil))
    inj2 := inject.New()
    inj2.Map(26)
    inj.SetParent(inj2)
    inj.Apply(&s)
    fmt.Println("s.Name =", s.Name)
    fmt.Println("s.Gender =", s.Gender)
    fmt.Println("s.Age =", s.Age)
}

运行结果:

s.Name = 张三
s.Gender = 男
s.Age = 26

拓展:inject 官方文档

– import “github.com/codegangsta/inject”

inject包提供了多种对实体的映射和依赖注入方式。

用法

func InterfaceOf

就是取类型,不过传入的参数要是接口类型的指针

例如

type SpecialString interface{}
func main(){
  fmt.Println(inject.InterfaceOf((*SpecialString)(nil))) // main.SpecialString
}
func InterfaceOf(value interface{}) reflect.Type

函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针,将抛出一个panic异常。

type Applicator

type Applicator interface {
    // 在Type map中维持对结构体中每个域的引用并用'inject'来标记
    // 如果注入失败将会返回一个error.
    Apply(interface{}) error
}

Applicator接口表示到结构体的依赖映射关系。

type Injector

type Injector interface {
    Applicator
    Invoker
    TypeMapper
    // SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖,
    // 将会继续从它的父injector中找,直到返回error.
    SetParent(Injector)
}

Injector接口表示对结构体、函数参数的映射和依赖注入。

func New

func New() Injector

New创建并返回一个Injector.

type Invoker

type Invoker interface {
    // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。
    // 它将返回reflect.Value的切片,其中存放原函数的返回值。
    // 如果注入失败则返回error.
    Invoke(interface{}) ([]reflect.Value, error)
}

Invoker接口表示通过反射进行函数调用。

type TypeMapper

type TypeMapper interface {
    // 基于调用reflect.TypeOf得到的类型映射interface{}的值。
    Map(interface{}) TypeMapper
    // 基于提供的接口的指针映射interface{}的值。
    // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
    MapTo(interface{}, interface{}) TypeMapper
    // 为直接插入基于类型和值的map提供一种可能性。
    // 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。
    Set(reflect.Type, reflect.Value) TypeMapper
    // 返回映射到当前类型的Value. 如果Type没被映射,将返回对应的零值。
    Get(reflect.Type) reflect.Value
}

TypeMapper接口用来表示基于类型到接口值的映射。

map()

传入一个参数,该参数的反射类型对象作为values的键,反射值对象作为values的值

mapTo()

传入两个参数,第一个参数的反射值对象作为values的值,第二个参数传入interfaceOf()获取其类型作为values的键

相关文章
|
7天前
|
Go
Go 语言循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。
17 1
|
6天前
|
Go
Go to Learn Go之反射
Go to Learn Go之反射
25 8
|
6天前
|
Go 开发者
探索Go语言的并发之美
在Go语言的世界里,"并发"不仅仅是一个特性,它是一种哲学。本文将带你领略Go语言中goroutine和channel的魔力,揭示如何通过Go的并发机制来构建高效、可靠的系统。我们将通过一个简单的示例,展示如何利用Go的并发特性来解决实际问题,让你的程序像Go一样,轻盈而强大。
|
7天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
7天前
|
Go
go语言创建字典
go语言创建字典
|
8天前
|
安全 Go 数据处理
探索Go语言的并发之美:Goroutines与Channels
在Go语言的世界里,"并发"不仅仅是一个概念,它是一种生活的方式。本文将带你领略Go语言中Goroutines和Channels的魔力,它们是如何让并发编程变得既简单又高效。我们将通过一个简单的示例,展示如何使用这些工具来构建一个高性能的网络服务。
|
8天前
|
关系型数据库 Go 数据处理
高效数据迁移:使用Go语言优化ETL流程
在本文中,我们将探索Go语言在处理大规模数据迁移任务中的独特优势,以及如何通过Go语言的并发特性来优化数据提取、转换和加载(ETL)流程。不同于其他摘要,本文不仅展示了Go语言在ETL过程中的应用,还提供了实用的代码示例和性能对比分析。
|
9天前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
9天前
|
Go
go语言注释,标识符 | 17
go语言注释,标识符 | 17
|
8天前
|
NoSQL Go API
go语言操作Redis
go语言操作Redis
下一篇
无影云桌面