We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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反射的使用方法和应用场景
Go语言自带的reflect包实现了在运行时进行反射的功能,这个包可以帮助识别一个interface{}类型变量其底层的具体类型和值。我们的createQuery函数接收到一个interface{}类型的实参后,需要根据这个实参的底层类型和值去创建并返回INSERT语句,这正是反射包的作用所在。
Go
reflect
interface{}
createQuery
INSERT
在开始编写我们的通用SQL生成器函数之前,我们需要先了解一下reflect包中我们会用到的几个类型和方法,接下来我们先逐个学习一下。
SQL
经过反射后interface{}类型的变量的底层具体类型由reflect.Type表示,底层值由reflect.Value表示。reflect包里有两个函数reflect.TypeOf() 和reflect.ValueOf() 分别能将interface{}类型的变量转换为reflect.Type和reflect.Value。这两种类型是创建我们的SQL生成器函数的基础。
reflect.Type
reflect.Value
reflect.TypeOf()
reflect.ValueOf()
让我们写一个简单的例子来理解这两种类型。
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{}类型实参对应的底层具体类型和值。
reflect.Typeof
reflect.Valueof
这里插播一下反射的三法则,他们是:
反射的第一条法则是,我们能够吧Go中的接口类型变量转换成反射对象,上面提到的reflect.TypeOf和 reflect.ValueOf 就是完成的这种转换。第二条指的是我们能把反射类型的变量再转换回到接口类型,最后一条则是与反射值是否可以被更改有关。三法则详细的说明可以去看看德莱文大神写的文章 Go反射的实现原理,文章开头就有对三法则说明的图解,再次膜拜。
reflect.TypeOf
reflect.ValueOf
下面我们接着继续了解完成我们的SQL生成器需要的反射知识。
reflect包中还有一个非常重要的类型,reflect.Kind。
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」。
main.order
Kind
map[string]string
注意除了reflect.Type外reflect.Value类型也实现了Kind方法返回reflect.Kind对象,这个不要迷惑到(其实NumField方法也是,Type和Value两个类型都实现的方法,应该是为了方便减少使用反射时的代码量)
我们可以通过reflect.StructField类型的方法来获取结构体下字段的类型属性。reflect.StructField可以通过reflect.Type提供的下面两种方式拿到。
reflect.StructField
// 获取一个结构体内的字段数量 NumField() int // 根据 index 获取结构体内字段的类型对象 Field(i int) StructField // 根据字段名获取结构体内字段的类型对象 FieldByName(name string) (StructField, bool)
reflect.structField是一个struct类型,通过它我们又能在反射里知道字段的基本类型、Tag、是否已导出等属性。
reflect.structField
type StructField struct { Name string Type Type // field type Tag StructTag // field tag string ...... }
与reflect.Type提供的获取Field信息的方法相对应,reflect.Value也提供了获取Field值的方法。
Field
func (v Value) Field(i int) Value { ... } func (v Value) FieldByName(name string) Value { ... }
这块需要注意,不然容易迷惑。下面我们尝试一下通过反射拿到order结构体类型的字段名和值
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,这个放在后面的文章我再总结吧,不然篇幅就太长了。
现在离完成我们的SQL生成器还差最后一步,即还需要把reflect.Value转换成实际类型的值,reflect.Value实现了一系列Int(), String(),Float()这样的方法来完成其到实际类型值的转换。
Int()
String()
Float()
上面我们已经了解完写这个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');
The text was updated successfully, but these errors were encountered:
No branches or pull requests
内容节选自我的原创文章用手写一个工具的过程讲清楚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
生成器函数的基础。让我们写一个简单的例子来理解这两种类型。
上面的程序会输出:
上面的程序里
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是可以互转使用的,不过在反射这块它们有挺大区别,从下面的程序中可以清楚地看到。上面的程序会输出
通过输出让我们清楚了两者之间的区别。
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
提供的下面两种方式拿到。reflect.structField
是一个struct类型,通过它我们又能在反射里知道字段的基本类型、Tag、是否已导出等属性。与
reflect.Type
提供的获取Field
信息的方法相对应,reflect.Value
也提供了获取Field
值的方法。这块需要注意,不然容易迷惑。下面我们尝试一下通过反射拿到
order
结构体类型的字段名和值上面的程序会输出:
除了获取结构体字段名称和值之外,还能获取结构体字段的Tag,这个放在后面的文章我再总结吧,不然篇幅就太长了。
reflect.Value转换成实际值
现在离完成我们的SQL生成器还差最后一步,即还需要把
reflect.Value
转换成实际类型的值,reflect.Value
实现了一系列Int()
,String()
,Float()
这样的方法来完成其到实际类型值的转换。用反射搞一个SQL生成器
上面我们已经了解完写这个SQL生成器函数前所有的必备知识点啦,接下来就把他们串起来,加工完成
createQuery
函数。这个SQL生成器完整的实现和测试代码如下:
可以把代码拿到本地运行一下,上面的例子会根据传递给函数不同的结构体实参,输出对应的标准
SQL
插入语句The text was updated successfully, but these errors were encountered: