gocookbook
gocookbook copied to clipboard
Go反射的使用教程
内容节选自我的原创文章用手写一个工具的过程讲清楚Go反射的使用方法和应用场景
Go语言的反射包
Go
语言自带的reflect
包实现了在运行时进行反射的功能,这个包可以帮助识别一个interface{}
类型变量其底层的具体类型和值。我们的createQuery
函数接收到一个interface{}
类型的实参后,需要根据这个实参的底层类型和值去创建并返回INSERT
语句,这正是反射包的作用所在。
在开始编写我们的通用SQL
生成器函数之前,我们需要先了解一下reflect
包中我们会用到的几个类型和方法,接下来我们先逐个学习一下。
reflect.Type 和 reflect.Value
经过反射后interface{}
类型的变量的底层具体类型由reflect.Type
表示,底层值由reflect.Value
表示。reflect
包里有两个函数reflect.TypeOf()
和reflect.ValueOf()
分别能将interface{}
类型的变量转换为reflect.Type
和reflect.Value
。这两种类型是创建我们的SQL
生成器函数的基础。
让我们写一个简单的例子来理解这两种类型。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
fmt.Println("Type ", t)
fmt.Println("Value ", v)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
上面的程序会输出:
Type main.order
Value {456 56}
上面的程序里createQuery
函数接收一个interface{}
类型的实参,然后把实参传给了reflect.Typeof
和reflect.Valueof
函数的调用。从输出,我们可以看到程序输出了interface{}
类型实参对应的底层具体类型和值。
Go语言反射的三法则
这里插播一下反射的三法则,他们是:
- 从接口值可以反射出反射对象。
- 从反射对象可反射出接口值。
- 要修改反射对象,其值必须可设置。
反射的第一条法则是,我们能够吧Go
中的接口类型变量转换成反射对象,上面提到的reflect.TypeOf
和 reflect.ValueOf
就是完成的这种转换。第二条指的是我们能把反射类型的变量再转换回到接口类型,最后一条则是与反射值是否可以被更改有关。三法则详细的说明可以去看看德莱文大神写的文章 Go反射的实现原理,文章开头就有对三法则说明的图解,再次膜拜。
下面我们接着继续了解完成我们的SQL生成器需要的反射知识。
reflect.Kind
reflect
包中还有一个非常重要的类型,reflect.Kind
。
reflect.Kind
和reflect.Type
类型可能看起来很相似,从命名上也是,Kind和Type在英文的一些Phrase是可以互转使用的,不过在反射这块它们有挺大区别,从下面的程序中可以清楚地看到。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
上面的程序会输出
Type main.order
Kind struct
通过输出让我们清楚了两者之间的区别。 reflect.Type
表示接口的实际类型,即本例中main.order
而Kind
表示类型的所属的种类,即main.order
是一个「struct」类型,类似的类型map[string]string
的Kind就该是「map」。
注意除了reflect.Type外reflect.Value类型也实现了Kind方法返回reflect.Kind对象,这个不要迷惑到(其实NumField方法也是,Type和Value两个类型都实现的方法,应该是为了方便减少使用反射时的代码量)
反射获取结构体字段的方法
我们可以通过reflect.StructField
类型的方法来获取结构体下字段的类型属性。reflect.StructField
可以通过reflect.Type
提供的下面两种方式拿到。
// 获取一个结构体内的字段数量
NumField() int
// 根据 index 获取结构体内字段的类型对象
Field(i int) StructField
// 根据字段名获取结构体内字段的类型对象
FieldByName(name string) (StructField, bool)
reflect.structField
是一个struct类型,通过它我们又能在反射里知道字段的基本类型、Tag、是否已导出等属性。
type StructField struct {
Name string
Type Type // field type
Tag StructTag // field tag string
......
}
与reflect.Type
提供的获取Field
信息的方法相对应,reflect.Value
也提供了获取Field
值的方法。
func (v Value) Field(i int) Value {
...
}
func (v Value) FieldByName(name string) Value {
...
}
这块需要注意,不然容易迷惑。下面我们尝试一下通过反射拿到order
结构体类型的字段名和值
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
if t.Kind() != reflect.Struct {
panic("unsupported argument type!")
}
v := reflect.ValueOf(q)
for i:=0; i < t.NumField(); i++ {
fmt.Println("FieldName:", t.Field(i).Name, "FiledType:", t.Field(i).Type,
"FiledValue:", v.Field(i))
}
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
上面的程序会输出:
FieldName: ordId FiledType: int FiledValue: 456
FieldName: customerId FiledType: int FiledValue: 56
除了获取结构体字段名称和值之外,还能获取结构体字段的Tag,这个放在后面的文章我再总结吧,不然篇幅就太长了。
reflect.Value转换成实际值
现在离完成我们的SQL生成器还差最后一步,即还需要把reflect.Value
转换成实际类型的值,reflect.Value
实现了一系列Int()
, String()
,Float()
这样的方法来完成其到实际类型值的转换。
用反射搞一个SQL生成器
上面我们已经了解完写这个SQL生成器函数前所有的必备知识点啦,接下来就把他们串起来,加工完成createQuery
函数。
这个SQL生成器完整的实现和测试代码如下:
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
type employee struct {
name string
id int
address string
salary int
country string
}
func createQuery(q interface{}) string {
t := reflect.TypeOf(q)
v := reflect.ValueOf(q)
if v.Kind() != reflect.Struct {
panic("unsupported argument type!")
}
tableName := t.Name() // 通过结构体类型提取出SQL的表名
sql := fmt.Sprintf("INSERT INTO %s ", tableName)
columns := "("
values := "VALUES ("
for i := 0; i < v.NumField(); i++ {
// 注意reflect.Value 也实现了NumField,Kind这些方法
// 这里的v.Field(i).Kind()等价于t.Field(i).Type.Kind()
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
columns += fmt.Sprintf("%s", t.Field(i).Name)
values += fmt.Sprintf("%d", v.Field(i).Int())
} else {
columns += fmt.Sprintf(", %s", t.Field(i).Name)
values += fmt.Sprintf(", %d", v.Field(i).Int())
}
case reflect.String:
if i == 0 {
columns += fmt.Sprintf("%s", t.Field(i).Name)
values += fmt.Sprintf("'%s'", v.Field(i).String())
} else {
columns += fmt.Sprintf(", %s", t.Field(i).Name)
values += fmt.Sprintf(", '%s'", v.Field(i).String())
}
}
}
columns += "); "
values += "); "
sql += columns + values
fmt.Println(sql)
return sql
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
e := employee{
name: "Naveen",
id: 565,
address: "Coimbatore",
salary: 90000,
country: "India",
}
createQuery(e)
}
可以把代码拿到本地运行一下,上面的例子会根据传递给函数不同的结构体实参,输出对应的标准SQL
插入语句
INSERT INTO order (ordId, customerId); VALUES (456, 56);
INSERT INTO employee (name, id, address, salary, country); VALUES ('Naveen', 565, 'Coimbatore', 90000, 'India');