golang 反射(reflect)
golang 反射(reflect)
反射是現代程序必備的元素,用於在 運行時 獲取程序元素,如對象等的 元數據,實現動態識別類型及其結構,以及相關的語義信息。
反射在程序中應用非常多,例如:
-
動態生成數據:json 序列化 / 反序列化;orm 映射, proxy 透明代理對象
-
動態調用方法:plugin 實現
-
框架自動處理程序:annotation tag 註解標籤
-
其他需要數據元數據的應用
在必要的場合,靈活應用反射,是中高級程序員能力的評價標準之一。靈活應用的根本是加深對 go 語言編譯與實現的理解,並閱讀典型應用案例。
濫用反射,也是低中級程序員最常見的問題,造成程序效率底下、不確定性錯誤增多。
一、Go 中的反射
go 是靜態語言,表示內存中任何一個數據對象(data object)的值及其類型必須是編譯期可確定的。因此,go 應用運行時不會像 java 等動態語言一樣,在運行期維護所有對象的元數據,以支持多態等需要。也不像 c 語言,不提供任何元數據支持。但註定 go 語言的反射是簡單和有限的。
大神文章,必讀!必讀!必讀!在短短的文章中,說明了 go 語言反射的要點!
- The Laws of Reflection 其中文翻譯 “反射三法則 https://blog.go-zh.org/laws-of-reflection”
請使用 $go tool tour
驗證該文中所有代碼!!!
這裏,僅提示其中要點:
-
類型與接口
-
向下類型轉換(DownCasting)。程序通過實現接口斷言完成。
接口對象.(斷言類型)
-
接口內部的對總是 (值, 具體類型) 的形式,而不會是 (值, 接口類型) 的形式
-
向上類型轉換(UpCasting)。編譯期完成,如果一個數據對象或接口的方法集合包含要轉換的類型的方法集合
-
空接口
interface{}
, 它是任何數據都包含的接口 -
Go 是靜態類型的語言。每個變量都有一種靜態類型
-
有一種重要的類別就是接口類型,它是一組方法的集合
-
接口值(內部表示) - 元組(數據對象的值,數據對象的靜態類型)
-
反射三法則
-
reflect.Value 是 原始對象值的 copy 是不可變的
-
要修改原始對象需要傳地址
-
v.Elem()
返回指針內容或接口值,它是可修改的 -
值可反射回接口值
-
特別注意
v
和v.Interface()
的區別 -
func (v Value) Interface() interface{}
-
v.Interface()
是反射的原始對象 -
v
是原始對象的 reflect.Value 值 -
變量 / 數據對象 – 反射 -> 值,具體類型
-
reflect 包中的兩種類型:Type 和 Value
-
Type 和 Value 的函數
TypeOf(i interface{})
,ValueOf(i interface{})
-
Kind 方法
-
重要:反射是編譯將值轉爲接口,TypeOf,ValueOf 只是簡單取出接口值的內容
-
go 反射的本質
-
reflect.Type 是接口
-
reflect.Value 是結構
-
Value 相關的構造函數,
Zero,NewAt,MakeSlice...
-
Type 相關的獲取函數
-
值不能離開類型而獨立存在, Value.Type() 獲取類型
-
Kind 是值和類型的共有方法, Kind 是 go 基礎類型的枚舉
-
反射是編譯期決定的擴展,go 運行期僅加載必要的元數據
-
根據接口的表示,反射對象類型不可能是接口
-
數據對象 – 編譯 -> 接口(值,具體類型)– 反射 -> 值,具體類型
-
1、反射是從接口值到反射對象
-
2、從反射對象可反射出接口值
-
3、要修改反射對象,其值必須可設置
-
結構體
-
type StructField
-
StructTag 的文字表達規範:key:”value”
-
demo
-
v.NumField()
獲得結構體 Field 數量 -
v.Field(i int)
獲取 Field 的值 -
v.FieldByXXX(...)
-
必須傳地址才能修改 struct 中的字段
-
獲取結構體 Fields (僅可導出的)
-
獲取結構體字段的 tag
-
獲取方法
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 反射中函數和方法的調用
二、golang 獲取包資源
程序中有許多資源,如配置文件、圖片、網頁等都是隨着包提供。對於 windows 程序或 java 程序都有 ResourceLoad 函數讀取運行程序(exe,dll,jar)中的資源。go 語言一般都源代碼提供,因此資源都是直接放置在包目錄下,而不打包。
go 包 爲你提供了按需管理程序資源的能力。其中,go/build 子包 是管理包以及應用環境最重要的包。
var Default Context = defaultContext()
Context 包含了程序構建工作區、版本等重要信息。
go tour 的源代碼,local.go 的 findRoot 函數提供查詢教學資源目錄的案例!https://github.com/golang/tour/blob/master/gotour/local.go
三、反射練習
設計一個簡單 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:"`
}
在 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 語句,並將結果集合根據數據類型自動映射到對象,並加入結果表。
提示
-
可以直接使用 database.sql 或使用 sqlt 。使用 sqlt 可以簡化程序開發,
-
任務 3 的結果映射,需要注意以下內容
-
在 rows.scan 前要生成制定類型的結構數據。可用
reflect.New(t)
函數 -
傳給 scan 的參數必須是地址,或實現 Scanner 的接口
-
參數數組中 field 地址順序,可以在 scan 前確定,也可以在 scan 後確定
package main
import (
"fmt"
_ "github.com/lib/pq"
"database/sql"
)
func main() {
db, _ := sql.Open(
"postgres",
"user=postgres db)
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)
}
}
}
轉自:
blog.csdn.net/pmlpml/article/details/78850516
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dF89ySHfv6rkhw2qT-jwqA