Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go反射的使用教程 #69

Open
kevinyan815 opened this issue Oct 26, 2021 · 0 comments
Open

Go反射的使用教程 #69

kevinyan815 opened this issue Oct 26, 2021 · 0 comments

Comments

@kevinyan815
Copy link
Owner

kevinyan815 commented Oct 26, 2021

内容节选自我的原创文章用手写一个工具的过程讲清楚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.Typereflect.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.Typeofreflect.Valueof 函数的调用。从输出,我们可以看到程序输出了interface{}类型实参对应的底层具体类型和值。

Go语言反射的三法则

这里插播一下反射的三法则,他们是:

  1. 从接口值可以反射出反射对象。
  2. 从反射对象可反射出接口值。
  3. 要修改反射对象,其值必须可设置。

反射的第一条法则是,我们能够吧Go中的接口类型变量转换成反射对象,上面提到的reflect.TypeOfreflect.ValueOf 就是完成的这种转换。第二条指的是我们能把反射类型的变量再转换回到接口类型,最后一条则是与反射值是否可以被更改有关。三法则详细的说明可以去看看德莱文大神写的文章 Go反射的实现原理,文章开头就有对三法则说明的图解,再次膜拜。

下面我们接着继续了解完成我们的SQL生成器需要的反射知识。

reflect.Kind

reflect包中还有一个非常重要的类型,reflect.Kind

reflect.Kindreflect.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.orderKind表示类型的所属的种类,即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'); 
@kevinyan815 kevinyan815 changed the title Go反射的应用场景和使用教程 Go反射的使用教程 Oct 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant