Go 语言反射(reflection)简述
Go 语言反射规则浅析
深度解密 Go 语言之反射
Go 语言反射的实现原理
DeepEqual
Go语言inject库:依赖注入
浅谈控制反转与依赖注入
(举两个例子,可能会更好一些,之后找一下用反射最成功的例子。)
反射功能强大,但是也是有一些 弊端 的:
所以:反射功能强大但代码可读性以及性能并不理想,若非必要并不推荐使用反射。
维基百科定义
在计算机科学中, 反射是指计算机程序 在运行时可以 访问、检测和修改它本身状态或行为的一种能力。用比喻来说, 反射就是程序在 运行的时候能够“观察”并且修改自己的行为
提取关键句子:反射是指在程序运行期对程序本身进行访问和修改的能力
很强大么?很强大。原因如下:
当没有反射时,对于静态语言来说,程序在编译的时候会将变量转化为内存地址,变量名并不会被编译器写入到可执行部分。所以,在运行过程中,就无法通过 变量名 获取自身的类型和值信息。
想要获取自身的相关信息,有且只有一个方法,通过内存地址获取。(太难了。。。。 )
而反射的作用:
使用反射在程序的编译期间就可以将变量的相关信息(如:类型和值信息等)整合到可执行文件中。
同时会提供反射接口让 程序 可以在运行期间访问到变量的相关信息。
最终,程序就可以在运行期间获取到变量的相关信息,并修改相关信息。
最后,再看一遍 Go 语言中对于反射的定义,就很容易去理解了:
Go 语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为 反射。
Go 语言通过官方提供的 reflect 包来访问程序的反射信息。
reflect 包中定义了 两个非常重要的类型:Type 和 Value。
Golang 中的任意类型 在反射中都可以被认为是由 reflect.Type 和 reflect.Value 组成的。
reflect 包中提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取 任意对象的 Type 和 Value。
在 Go 语言中,使用 reflect.TypeOf() 函数获取任意值的类型对象(reflect.Type)。
通过该类型对象可以访问任意值的类型信息。
// 自定义一个 MyInt 类型
type MyInt int
// 声明一个空结构体
type MyStruct struct {}
func main() {
// 定义一个变量
var a MyInt
typeOfA := reflect.TypeOf(a)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind()) // MyInt int
// 获取结构体实例的反射类型对象
typeOfB := reflect.TypeOf(MyStruct{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfB.Name(), typeOfB.Kind()) // MyStruct struct
}
代码说明:
例如:var a MyInt 中,它的类型对象的 Name:MyInt,Kind:int。
类型对象中,还有一个常用的方法是 Type.Elem(),它是用于获取 指针 所指向的值的类型的。
如以下代码所示:
// 自定义一个 MyInt 类型
type MyInt int
// 声明一个空结构体
type MyStruct struct {}
func main() {
// 获取结构体实例的反射类型对象
typeOfB := reflect.TypeOf(&MyStruct{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfB.Name(), typeOfB.Kind()) // ptr
fmt.Println(typeOfB.Elem(), typeOfB.Elem().Name(), typeOfB.Elem().Kind()) // model.MyStruct MyStruct struct
}
代码说明
当然,Type 的方法还有很多,如
当需要某些方法时,可以自行在源代码中寻找。
reflect.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.
//
// Only exported methods are accessible and they are sorted in
// lexicographic order.
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 for a defined type.
// For other (non-defined) types it returns the empty string.
Name() string
...
在 Go 语言中,使用 reflect.ValueOf() 函数获取任意变量的值对象(reflect.Type)。
通过该类型对象可以访问任意变量的值对象信息。
如果需要获取结构体中的变量信息,就需要使用 ValueOf 获取到结构体中的值,进而通过其他方法获取其他信息
不像 reflect.Type 类型是一个接口,reflect.Value 的类型被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法。
type Value struct {
// 包含过滤的或者未导出的字段
}
func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
...
关于 reflect.Type 和 reflect.Value 的其他具体方法的使用,可以看接下来的反射三大法则
这是 Golang 官方博客中提供使用反射的三大法则。具体有如下三大法则
接下来,开始一条一条的进行讲解
Reflection goes from interface value to reflection object
这个很容易理解,讲的是:
反射可以将 接口类型(interface)变量 转化成 反射类型变量(指 reflect.Type 和 reflect.Value 类型)。而任意类型的变量都可以实现接口方法(Ducking Type),所以,任意类型的变量都可以转化成 反射类型的变量。
这个在之前的介绍中也提到了,接口类型变量 转化成 反射类型变量 主要是通过 reflect.TypeOf 和 reflect.ValueOf 两个方法。
通过这两个方法分别可以获取变量的类型和变量的值 ==(等价于) 获取了变量的全部信息。
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x)) // type:float64
fmt.Println("value:", reflect.ValueOf(x)) // value:3.4
}
Reflection goes from reflection object to interface value
这个也容易理解,反射可以正向转化,也可以反向转化
反射可以将 反射类型对象 转化成 接口类型对象
反射类型对象 内部有一个方法(func Interface() interface {}),可以将自身对象转成 interface 类型的变量
那这么做,有什么好处呢?先看如下代码
func main() {
a := 64.0
v := reflect.ValueOf(a)
fmt.Printf("type:%T\n", v)
fmt.Println(v)
fmt.Printf("type:%T\n", v.Interface())
fmt.Println(v.Interface())
fmt.Printf("value is %7.1e\n", v.Interface())
// 打印输出:
// type:reflect.Value
// 64
// type:float64
// 64
// value is 6.4e+01
}
可以看到:fmt.Println 的函数接收一个接口类型的变量。
而 v 的类型是 reflect.Value 类型的。
相当于在 fmt.Println 内部仍然还需要再转化一次,通过接口断言才能获取 v 的值。
而 v.Interface() 函数直接返回的就是 interface 类型对象。
该接口变量内部包含了具体值的类型信息,Printf 函数不需要做转化以及类型断言,就直接可以恢复类型信息。
总结:当然,我们在使用过程中,无论用不用 Interface() 方法,都能得出正确的结果。只不过无非就是资源消耗多少的问题。
To modify a reflection object, the value must be settable
这个法则说的是,如果要修改 反射类型对象 ,其值必须是可设置的
“其值必须是可设置的”,指的是传值时,需要传指针类型(即引用类型)的。这也很容易理解。
Go 语言中函数调用值类型时,相当于重新复制一个值,所以得到的 反射对象 就与 变量 没有任何关系,那么直接修改 反射对象 无法改变原始变量。
如果是指针,就不存在如上问题,可以直接通过指针修改变量的值。
func main() {
i := 1
v := reflect.ValueOf(i)
v.Elem().SetInt(10)
fmt.Println(i)
}
// 上面的代码运行会报 panic 错误。
func main() {
i := 1
v := reflect.ValueOf(&i)
v.Elem().SetInt(10)
fmt.Println(i) // 10
}
// 这个程序则可以运行,发行 i 的值也改变了。
代码说明
反射对于基本类型,可以根据以上三大法则对其进行使用,但是还有两个比较特殊的结构,需要另说一下,一个是结构体的反射,另一个是在反射中对函数的调用。
当使用反射对结构体中的数据进行修改时,需要保证两点:
一、需要结构体的指针(原因在第三法则时说了)。
二、需要结构体中的字段是可导出的(首字母需要大写)
这第二点,也很好理解。之前在接口那一节中也说过,结构体中的字段必须是可导出的(首字母大写),这样外部的包才能访问该字段。否则,它就是私有的,无法外部访问。只有能访问了,才有可能去修改:
// 声明一个结构体
type MyStruct struct {
Name string
}
func main() {
stu := MyStruct{Name: "Asa"}
// 获取结构体实例的反射类型对象
typeOfB := reflect.ValueOf(&stu)
// 显示反射类型对象的名称和种类
s := typeOfB.Elem()
s.Field(0).SetString("123")
fmt.Println(typeOfB.Kind(), stu) // MyStruct struct
}
代码说明
代码中有两点需要注意:
Field 方法:结构体中一般包含一个或多个变量,这时就需要通过 Field 方法获取固定位置的变量。
Field 方法的功能介绍
Field returns the i'th field of the struct v.
Field 返回结构体 v 的第 i 个字段。
It panics if v's Kind is not Struct or i is out of range.
如果 v 的类型不是结构体,或者 i 超出了范围,则会报 panic。
setString 方法:使用反射修改变量的值时,需要根据变量的类型使用不同的set方法修改值。例如,这次 Name 的类型是 string,所以使用 setString 方法。
再往上的代码中,i 是int 类型,所以使用反射中的 setInt 方法修改值。如果使用的方法不对,程序运行时,将会报 panic 。
setString 方法的功能介绍
SetString sets v's underlying value to x
SetString将 v 的基础值设置为 x 。
It panics if v's Kind is not String or if CanSet() is false
如果 v 的类型不是 String 或者 CanSet() 为 false,则会产生panic。
那 CanSet() 方法又是什么呢?
它可以预先判断 传入的变量是否为指针变量;如果是结构体指针中的变量,还会判断该变量是否为可导出状态(首字母是否大写)
那为什么需要这个方法呢?如果不能修改的话,不是有报错提醒吗?
回答:对啊,是有报错提醒,但需要知道,那是程序运行时的报错(会让程序崩溃的),若只是编译不运行这个代码,将永远不会报错。而 CanSet() 可以在 set 之前做判断,如果不能修改,则打印输出,或者其他方法提醒管理员。而不会使程序报 panic ,导致程序崩溃。
既然函数也实现了空接口方法,那么它也是可以成为 反射类型对象 的。
那么,如下代码则显示了如何通过 反射类型变量 调用函数的:
// 普通函数
func add(a, b int) int {
return a + b
}
func main() {
// 将函数包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int())
}
代码说明如下
看完上述代码,就能很容易理解了 反射类型对象 调用函数的全过程
但是这个函数调用有什么用么?它能应用于什么场景? 应用于:inject 库中。
(在实际生产中,见过写的超级优雅的反射案例,实现了类似于 泛型的功能,之后有机会再修改内容吧)
在反射的实际应用场景中,有两个很常见的操作,其内部都是调用的反射机制:Json 序列化,以及 DeepEqual 。
还有一个 inject 库是通过反射机制实现的依赖注入,可以了解以下。
Json 序列化的主要用途就是将 结构体数据转化成 json 字符串数据,大多是用于服务器之间的通信,以及前后端的数据传输。
日常生活中最常用的方法有两种: 序列化 和 反序列化
序列化:将 结构体 转成 json 字符串
使用方法:json.Marshal(v interface{})([]byte, error)
应用场景
type student struct {
name string
Id int
Score float64
}
func main() {
stu := student{"asa", 123, 100}
jsonStu, err := json.Marshal(stu)
if err != nil {
fmt.Println("json err")
}
fmt.Println(string(jsonStu)) // {"Id":123,"Score":100}
}
代码说明
可以看到一个现象,在输出的 json 字符串中,并没有输出 name 属性。是因为 name 首字母没有大写,无法被外部的外部的方法捕获到。(正如上文讲结构体时也说过这一问题)
反序列化:将 json 字符串 填充到 结构体 \n \n 使用方法:json.Unmarshal(data []byte, v interface{}) erro
应用场景
type student struct {
name string
Id int
Score float64
}
func main() {
stuStr := "{"name":"asa","Id":123,"Score":100}"
stu := student{}
json.Unmarshal([]byte(stuStr), &stu)
fmt.Println(stu) // { 123 100}
}
代码说明
可以看到有两个奇特之处
DeepEqual 是反射中的一个方法,主要用于 Golang 中的深度比较。
当需要比较结构体中数据,或者切片,map 中的数据时,就可以使用这个方法。
在Golang 中,slice can only be compared to nil
type student struct {
name string
Id int
Score float64
}
func main() {
stu := student{"asa", 123, 100}
stu2 := student{name: "asa", Score: 100,Id: 123}
fmt.Println(reflect.DeepEqual(stu , stu2)) // true
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
fmt.Println(reflect.DeepEqual(s1, s2)) // true
}
更多使用 DeepEqual 方法可以参考DeepEqual
这是一种第三方包,有许多大厂都实现了 Inject 库。在此主要介绍一下 inject 库 的用途,之后有用到的地方,可以自行去寻找是实现方法。
在介绍 inject 之前,先要了解 “依赖注入” 和 “控制反转” 概念。
控制反转(IOC Inversion Of Control):如果一个类A 的功能实现需要借助于类B,那么就称类B是类A的依赖,如果在类A的内部去实例化类B,那么两者之间会出现较高的耦合,一旦类B出现了问题,类A也需要进行改造,如果这样的情况较多,每个类之间都有很多依赖,那么就会出现牵一发而动全身的情况,程序会极难维护,并且很容易出现问题。要解决这个问题,就要把A类对B类的控制权抽离出来,交给一个第三方去做,把控制权反转给第三方,就称作控制反转(IOC Inversion Of Control)
依赖注入是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的一种实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。
控制反转的价值在于解耦,使用控制反转,就无需写许多的 init 结构体的方法,对于之后程序的维护和扩展有非常大的帮助。
读完以下这篇文章就很容易能理解了依赖注入的重要性
浅谈控制反转与依赖注入
还有反射源码分析,还要写么
之后再说吧 。。。。
请多多评论,有问题或者需要修改地方请指出来,我会及时回复的!!!