反射是现代程序必备的元素,用于在 运行时 获取程序元素,如对象等的 元数据,实现动态识别类型及其结构,以及相关的语义信息。
反射在程序中应用非常多,例如:
在必要的场合,灵活应用反射,是中高级程序员能力的评价标准之一。灵活应用的根本是加深对 go 语言编译与实现的理解,并阅读典型应用案例。
滥用反射,也是低中级程序员最常见的问题,造成程序效率底下、不确定性错误增多。
go 是静态语言,表示内存中任何一个数据对象(data object)的值及其类型必须是编译期可确定的。因此,go 应用运行时不会像 java 等动态语言一样,在运行期维护所有对象的元数据,以支持多态等需要。也不像 c 语言,不提供任何元数据支持。 但注定 go 语言的反射是简单和有限的。
大神文章,必读!必读!必读!在短短的文章中,说明了 go 语言反射的要点!
请使用 $go tool tour
验证该文中所有代码!!!
这里,仅提示其中要点:
类型与接口
interface{}
, 它是任何数据都包含的接口接口对象.(断言类型)
反射三法则
TypeOf(i interface{})
, ValueOf(i interface{})
Zero,NewAt,MakeSlice...
v
和 v.Interface()
的区别 v.Interface()
是反射的原始对象v
是原始对象的 reflect.Value 值v.Elem()
返回指针内容或接口值,它是可修改的结构体
v.NumField()
获得结构体 Field 数量v.Field(i int)
获取 Field 的值v.FieldByXXX(...)
获取方法
package main
import "fmt"
import "reflect"
type T struct {
A int
B string
}
func (t *T) SetA(i int) {
t.A = i
}
func main() {
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
typePT := reflect.TypeOf(&t)
fmt.Printf("%d\n",typePT.NumMethod())
for i := 0; i < typePT.NumMethod(); i++ {
m := typePT.Method(i)
fmt.Printf("%d: %s %v\n", m.Index,m.Name,m.Type)
}
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
//调用方法/函数
m := typePT.Method(0)
params := make([]reflect.Value,2)
params[0] = reflect.ValueOf(&t)
params[1] = reflect.ValueOf(5)
m.Func.Call(params)
fmt.Println("t is now", t)
}
参考:golang反射中函数和方法的调用
程序中有许多资源,如配置文件、图片、网页等都是随着包提供。对于 windows 程序或 java 程序都有 ResourceLoad 函数读取运行程序(exe,dll,jar)中的资源。go 语言一般都源代码提供,因此资源都是直接放置在包目录下,而不打包。
go 包 为你提供了按需管理程序资源的能力。其中,go/build 子包 是管理包以及应用环境最重要的包。
var Default Context = defaultContext()
Context 包含了程序构建工作区、版本等重要信息。
go tour 的源代码,local.go 的 findRoot 函数提供查询教学资源目录的案例!
设计一个简单 ORMEngin 对象,使它完成以下任务:
数据库表
CREATE TABLE `userinfo` (
`uid` INT(10) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NULL DEFAULT NULL,
`departname` VARCHAR(64) NULL DEFAULT NULL,
`created` DATE NULL DEFAULT NULL,
PRIMARY KEY (`uid`)
);
1、orm 规则
我们在field对应的Tag中对Column的一些属性进行定义,例如:
// UserInfo .
type UserInfo struct {
UID int `orm:"id,auto-inc,type=INT(10)"` //语义标签
UserName string
DepartName string
CreateAt *time.Time `orm:"name=created" json:",omitempty"`
}
在 orm 标签中,用“,”号作为属性的分割,每个属性为“key=value”。如果只有key,表示它是 Bool 属性,默认是 true。例如:id 表示这个字段是关键字。 更多字段属性可参考 Column属性定义 ,也可以用自己定义的规则和 key。
2、实现自动插入数据
用户的样例代码:
user := UserInfo{...}
affected, err := engine.Insert(user)
// INSERT INTO user (name) values (?)
要求利用反射技术,根据输入数据的类型自动生成插入 sql 语句,实现函数 Insert(o interface{})
3、实现查询结果自动映射
用户的样例代码:
pEveryOne := make([]*Userinfo, 0)
err := engine.Find(&pEveryOne)
// SELECT `col-name`,`col-name` ... FROM UserInfo
要求利用反射技术,根据输入数据的类型自动生成查询 sql 语句,并将结果集合根据数据类型自动映射到对象,并加入结果表。
提示
reflect.New(t)
函数附: scan 后确定映射的参考代码,它来自如何在 golang 使用反射调用扫描可变参数函数?
package main
import (
"fmt"
_ "github.com/lib/pq"
"database/sql"
)
func main() {
db, _ := sql.Open(
"postgres",
"user=postgres dbname=go_testing password=pass sslmode=disable")
rows, _ := db.Query("SELECT * FROM _user;")
columns, _ := rows.Columns()
count := len(columns)
values := make([]interface{}, count)
valuePtrs := make([]interface{}, count)
for rows.Next() {
for i, _ := range columns {
valuePtrs[i] = &values[i]
}
rows.Scan(valuePtrs...)
for i, col := range columns {
var v interface{}
val := values[i]
b, ok := val.([]byte)
if (ok) {
v = string(b)
} else {
v = val
}
fmt.Println(col, v)
}
}
}