以下內容為個人學習總結,如果有不准確的地方,歡迎指出!
說實話我之前用Python基本沒怎么用過反射,估計在Golang里面也一樣,在大多數應用和服務中並不常見。
提到反射,就必須要提一下Golang反射的三大定律
- 1 可以將
interface{}
類型轉換為reflect
類型。 - 2 通過反射對象可以獲取
interface{}
變量。 - 3 值是否可以被更改,能被尋址。(概念不好理解,后面demo解釋)
原文
- 1 Reflection goes from interface value to reflection object.
- 2 Reflection goes from reflection object to interface value.
- 3 To modify a reflection object, the value must be settable.
三大定律原出處:
https://blog.golang.org/laws-of-reflection
反射的一般使用場景
- 不確定預定類型的參數,需要根據參數的類型來執行不同的操作。
當然也可以使用
Assertion
來判斷類型,但是這種方式非常麻煩。
// 通過斷言判斷類型
func AssertionType(v interface{}){
switch v.(type){
case int, int16, int32, int64:
fmt.Printf("整數類型 %d \n", v)
case userInfo:
fmt.Printf("是 userInfo 類型 %v ", v)
fmt.Printf("年齡 %d \n", v.(userInfo).Age)
// 斷言調用方法
v.(userInfo).SayAge()
default:
fmt.Println("沒有匹配到")
}
}
"但是我們如何處理其它類似[]float64、map[string][]string等類型呢?我們當然可以添加更多的測試分支,但是這些組合類型的數目基本是無窮的。還有如何處理類似url.Values這樣的具名類型呢?即使類型分支可以識別出底層的基礎類型是map[string][]string,但是它並不匹配url.Values類型,因為它們是兩種不同的類型,而且switch類型分支也不可能包含每個類似url.Values的類型,這會導致對這些庫的依賴。"--出自Go語言程序設計
沒有辦法來檢查未知類型的表示方式,我們被卡住了。這就是我們為何需要反射的原因。
Golang中反射常用的方法
reflect.TypeOf
獲取類型 以及相關信息(對應第一條定律)reflect.ValueOf
獲取數據的運行時的表示(對應第二條定律)
其他基礎方法(部分)
- 結構體反射常用 假設
user
為實例化結構體- reflect.TypeOf(user).Field(int) // 獲取 指定結構體下角標屬性的類型
- reflect.TypeOf(user).Field(int).Tag.Get("foo") // 獲取指定下角標屬性的結構體標簽 內部再調用 Lookup方法
- reflect.ValueOf(user).NumField() // 獲取結構體屬性數量
- reflect.ValueOf(user).Kind() // 獲取對應的類型
- reflect.ValueOf(user).MethodByName("SayName").Call([]reflect.Value{reflect.ValueOf("測試")}) // 傳參數調用方法
- map slice獲取獲取 (n為map或者slice)
- reflect.ValueOf(n).Len() // 獲取長度
一些關於反射的演示
// 創建一個結構體
type userInfo struct {
User string `json:"user"foo:"test_tag"`
Avatar string `json:"avatar"bar:""`
Age int `json:"age"`
}
// 定義兩個方法
func (u userInfo) SayName(name string){
fmt.Printf("用戶名是 %s\n", name)
}
// 方法
func (u userInfo) SayAge(){
fmt.Printf("年齡是 %d\n", u.Age)
}
func TestReflect(t *testing.T) {
// 實例化結構體
user := userInfo{User: "Nike", Age:18}
// 斷言判斷類型 功能不如反射強大
AssertionType(user)
AssertionType(1111)
typ := reflect.TypeOf(user) // 獲取reflect的類型
t.Log(typ)
val := reflect.ValueOf(user) // 獲取reflect的值
t.Log(val)
kd := val.Kind() // 獲取到st對應的類別
t.Log(kd)
num := val.NumField() // 獲取值字段的數量
t.Log(num)
// 通過反射調用方法
m1 := val.MethodByName("SayName")
//m1 := val.Method(0)
m1.Call([]reflect.Value{reflect.ValueOf("測試")}) // 傳參數
// 私有方法不可反射調用 Java反射可以暴力調用私有方法
m2 := val.MethodByName("SayAge")
m2.Call([]reflect.Value{}) // 不傳參數
tagVal := typ.Field(2) // 獲取index為2的類型信息
val = val.Field(2) // 獲取index為2實例化后的值
t.Log(tagVal)
t.Log(val)
t.Log(tagVal.Type) // 輸出結構體字段的類型
t.Log(tagVal.Name) // 輸出結構體字段名稱
// 獲取結構體的tag 沒有則為空 Get 實際就是調用的Lookup
t.Log(tagVal.Tag.Get("foo"))
// 返回兩個值 第一個為tag值 第二個為bool值 true表示設置了此tag 無論是否為空字符串
t.Log(tagVal.Tag.Lookup("foo"))
t.Log(typ.Field(1).Tag.Lookup("bar")) // 設置了tag為bar 但是為空字符串 依舊為true
t.Log(typ.Field(1).Tag.Lookup("any_tag")) // 沒有設置此tag 就為false
// 必須使用地址 才可以修改原來的值 否則會panic (反射第三定律,值可以被修改)
modifyVal(&user)
t.Log(user)
}
// 通過反射修改值
func modifyVal(user interface{}){
// 獲取變量的指針
pVal := reflect.ValueOf(user) // 獲取reflect的值
// 獲取指針指向的變量
v := pVal.Elem()
// 找到並更新變量的值
v.FieldByName("User").SetString("Jack")
}
反射的簡單應用
最簡單的例子,通過反射對比slice
或者map
是否相等
規定Golang中slice,map,func不能用 == 比較
https://stackoverflow.com/questions/37900696/why-cant-go-slice-be-used-as-keys-in-go-maps-pretty-much-the-same-way-arrays-ca
https://golang.org/ref/spec#Comparison_operators
sliceA := []int{1, 2, 3}
sliceB := []int{1, 2, 3}
//fmt.Println(sliceA == sliceB) // panic
// 可以用反射對比
fmt.Println(reflect.DeepEqual(sliceA, sliceB))
利用反射設計一個校驗參數的方法
主要參考 gin-vue-admin 參數驗證 我自己添加了一個正則校驗的方法。
比如定義一個verify
來驗證結構體里面的方法是否合法
// 存放驗證規則的地方 一個字段可以有多個校驗方法
type Rules map[string][]string
var (
UserInfoVerify = Rules{"Page": {Ge("1")}, "PageSize": {Le("50")}, "Name": {Regex(`^\d{3}$`), NotEmpty()}}
)
type UserInfo struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Name string `json:"name"`
}
func TestVerify(t *testing.T) {
// 參數信息 可以和請求的參數信息綁定
u := UserInfo{Page: 1, PageSize: 30, Name: "234"} // 合法
//u := UserInfo{Page:1, PageSize:30, Name: "1234"} // Name非法
// 驗證參數是否合法 verify 自定義的校驗蠶農書
if err := verify(u, UserInfoVerify); err != nil {
t.Log(fmt.Sprintf("驗證失敗 %s \n", err))
} else {
t.Log("success")
}
}
// 核心函數
func verify(st interface{}, roleMap Rules) (err error) {
// 限定 比較返回值為 以下幾個
compareMap := map[string]bool{
"lt": true,
"le": true,
"eq": true,
"ne": true,
"ge": true,
"gt": true,
}
typ := reflect.TypeOf(st)
val := reflect.ValueOf(st)
// 判斷待驗證參數 是否是結構體 不是直接返回錯誤
if val.Kind() != reflect.Struct {
return errors.New("expect struct")
}
// 遍歷結構體的所有字段
for i := 0; i < val.NumField(); i++ {
// 獲取反射后的具體字段
tagVal := typ.Field(i)
val := val.Field(i)
// 判斷此字段是否有校驗規則 >0 則說明有
if len(roleMap[tagVal.Name]) > 0 {
// 循環此字段的校驗規則(一個字段可以存在多個校驗規則) 規則為 各個判斷類型函數 返回值
for _, v := range roleMap[tagVal.Name] {
switch {
// 非空判斷
case v == "notEmpty":
if isBlank(val) {
return errors.New(tagVal.Name + "值不能為空")
}
// 正則校驗
case strings.Split(v, "=")[0] == "regex":
if !isRegexMatch(val.String(), v) {
return errors.New(tagVal.Name + "正則校驗不合法" + v)
}
// 比較符判斷 分割返回值里面的 = 符號 compareMap 確保輸入的函數正確
case compareMap[strings.Split(v, "=")[0]]:
// 比較值 val 為反射字段后的值 v為 lt=1等校驗值
if !compareVerify(val, v) {
return errors.New(tagVal.Name + "長度或值不在合法范圍," + v)
}
default:
fmt.Println("檢查 Rules 校驗函數輸入是否正確: " + v)
}
}
}
}
return nil
}
以上代碼不完整,完整代碼見GitHub地址
反射應用參數校驗代碼地址
原文地址見個人博客