本篇不會介紹反射的基本概念和原理等,會從每一個經常使用的方法入手,講解一些基本和進階用法,反射不太適合在業務層使用,由於會幾何倍的下降運行速度,並且用反射作出來的程序健壯度不高,一旦一個環節沒有處理好就會直接panic,影響程序的運行,可是在後臺上使用仍是很適合的,能夠極大的下降代碼量,從繁複的增刪改查操做和無邊的拋err(面向錯誤編程,太貼切了)中解脫出來。git
能夠獲取任何變量的類型對象,使用該對象能夠獲取變量的Name
和Kind
,Name
表明的是變量類型的名稱,Kind
表明的是變量的底層類型名稱,如下是兩個典型的例子。github
// 系統變量 str := "張三" reflectType := reflect.TypeOf(str) fmt.Printf("name: %v kind: %v", reflectType.Name(), reflectType.Kind()) // name: string kind: string // 自定義變量 type person string a := person("張三") reflectType := reflect.TypeOf(a) fmt.Printf("name: %v kind: %v", reflectType.Name(), reflectType.Kind()) // name: person kind: string
Elem()
方法主要用來獲取指針
類型(只能使用在數組、chan、map、指針、切片幾個類型上)的類型對象數據庫
str := "張三" reflectType := reflect.TypeOf(&str) reflectElem := reflectType.Elem() fmt.Printf("name: %v kind: %v", reflectElem.Name(), reflectElem.Kind()) // name: string kind: string
能夠獲取任意變量的值對象,它的類型是reflect.Value
,使用該對象一樣能夠獲取變量的Name
和Kind
,經過獲取Kind
可使用類型斷言獲取變量的值。編程
這裏的
reflect.ValueOf
其實做用不大,在實際應用場景中多先使用reflect.ValueOf
獲取變量的reflect.Value
而後接Interface()
方法把變量轉化爲Interface{}
類型,獲取reflect.Value
的方法多采用reflect.TypeOf()
加reflect.New()
方法,後面實戰部分會有詳細用法。json
判斷值對象是否爲nil,只能對通道、切片、數組、map、函數、interface等使用。api
判斷值對象是否爲有效值,即非其默認0值,例如數字類型的0,字符串類型的"",在實際使用中,若是不對這些值進行處理,可能會直接panic。數組
配合reflect.TypeOf
返回單個類型的切片類型。app
str := "張三" reflectType := reflect.TypeOf(str) reflectSlice := reflect.SliceOf(reflectType) fmt.Printf("name: %v kind: %v", reflectSlice.Name(), reflectSlice.Kind()) // name: kind: slice // 獲取切片中元素的值 a := []int{8, 9, 10} reflectType := reflect.ValueOf(a) for i := 0; i < reflectType.Len(); i++ { fmt.Println(reflectType.Index(i)) } // 8 9 10
這裏注意數組、指針、切片、map等一些類型是沒有類型名稱的。函數
配合reflect.TypeOf
實例化一個該類型的值對象,返回該值對象的指針(想要使用反射設置值,必須使用指針)。指針
str := "張三" reflectType := reflect.TypeOf(str) reflectValue := reflect.New(reflectType) // 設置值 reflectValue.Elem().SetString("李四") fmt.Printf("value: %v kind: %v", reflectValue.Elem(), reflectValue.Elem().Kind()) // value: 李四 kind: string
返回值對象的指針。
str := "張三" reflectType := reflect.TypeOf(str) if reflectType.Kind() != reflect.Ptr { reflectType = reflect.PtrTo(reflectType) } fmt.Printf("value: %v kind: %v", reflectType, reflectType.Kind()) // value: *string kind: ptr
上面的幾個方法只是開胃菜,真正經常使用仍然是結構體的反射,業務中各類增刪改查操做都要經過數據庫完成,而數據庫交互使用的都是結構體,這裏會先列出一些結構體反射要用到的方法,而後經過一篇後臺公用model類的實戰來完成這篇的內容。
和上面幾個基本方法有關的內容這裏就再也不贅述,有興趣的能夠本身私底下去試試,這裏只針對一些結構體的專用方法進行說明。
返回結構體的字段數量,NumField()
使用的對象必須是結構體,不然會panic。
type Student struct { Name string Age int } a := &Student{ Name: "張三", Age: 18, } reflectValue := reflect.ValueOf(a) fmt.Println(reflectValue.Elem().NumField()) // 2
經過字段的索引獲取字段的值,從0開始,順序參照結構體定義時的由上到下的順序。
a := &Student{ Name: "張三", Age: 18, } reflectValue := reflect.ValueOf(a) for i := 0; i < reflectValue.Elem().NumField(); i++ { fmt.Println(reflectValue.Elem().Field(i)) }
經過字段名稱獲取字段的值。
a := &Student{ Name: "張三", Age: 18, } reflectValue := reflect.ValueOf(a) fmt.Println(reflectValue.Elem().FieldByName("Name")) // 張三
返回結構體的方法數量。
根據傳入的匿名函數返回對應名稱的方法。
直接經過方法的索引,返回對應的方法。
經過方法名稱返回對應的方法。
以上四個方法相關的函數就不放例子了,經過對應的函數獲取到方法後,使用Call()
進行調用,其中特別注意的是,調用時傳入的參數必須是[]reflect.Value
格式的。
這裏用到的數據庫類爲gorm本篇不探討其相關知識,若有疑惑,請自行實踐。
首先編寫model,根目錄下建立文件夾model,在model文件夾中建立search.go
// Student 學生 type Student struct { Name string Age int ID int } // TableName 表名 func (Student) TableName() string { return "student" }
編寫實現公用方法的接口,根目錄下建立search.go
// SearchModel 搜索接口 type SearchModel interface { TableName() string } // SearchModelHandler 存儲一些查詢過程當中的必要信息 type SearchModelHandler struct { Model SearchModel } // GetSearchModelHandler 獲取處理器 func GetSearchModelHandler(model SearchModel) *SearchModelHandler { return &SearchModelHandler{ Model: model, } } // Search 查找 func (s *SearchModelHandler) Search() string { query := db.model(s.Model) itemPtrType := reflect.TypeOf(s.Model) if itemPtrType.Kind() != reflect.Ptr { itemPtrType = reflect.PtrTo(itemPtrType) } itemSlice := reflect.SliceOf(itemPtrType) res := reflect.New(itemSlice) // 這一步相當重要,雖然Scan方法接收的是一個interface{}類型,可是由於咱們這裏傳入的SearchModel,若是直接使用s.Model執行傳入會報錯 // 緣由在於這裏的Scan的interface和咱們傳入的model實現的是不一樣的接口,Scan只認識gorm包中定義的接口類型 err := query.Scan(res.Interface()).Error if err != nil { // 這裏不要學我 panic("error") } ret, _ := json.Marshal(res) return string(ret) }
就這樣一個簡單的公用類就誕生了,接下來就是調用了,在更目錄下建立main.go
func main() { handler := GetSearchModelHandler(&model.Student{}) handler.Search() }
好比咱們還有一個班級表,而在返回學生信息的時候須要加上班級信息,這該怎麼操做呢,這裏我只提供本身的一種思路,若是有更好的建議,請寫在下方的評論裏 共同交流。
首先,建立class的結構體,在model文件夾內建立class.go
// Class 班級 type Class struct { ID int Name string } // TableName 表名 func (Class) TableName() string { return "class" }
而後編寫一個公用的接口,在model文件夾下建立文件additional_api.go
// AdditionalInfo 附加信息獲取幫助 type AdditionalInfo struct { FieldName string Method func(ids []int32) string } // MinMapAPI 獲取總內容接口,至關於實戰一中的SearchModel type MinMapAPI interface { TableName() string } // MinMapInterface 最小信息獲取接口 type MinMapInterface interface { TransFields() string }
上面的方法先定義好,後面有用,而後修改model的內容,打開class.go輸入
// ClassMin 最小班級信息 type ClassMin struct { ID int Name string } // TransFields 轉換名稱,填寫你要獲取的字段的名稱 func (c *ClassMin) TransFields() string { return "Name" }
接下來編寫具體獲取附加信息的方法,打開additional_api.go,輸入如下內容
// GetMinMap 獲取最小信息 func GetMinMap(ids []int32, model MinMapAPI, minModel MinMapInterface) string { // 獲取總數據的切片 modelType := reflect.TypeOf(model) modelSliceType := reflect.SliceOf(modelType) res := reflect.New(modelSliceType) err := db.Model(model).Where("id in (?)", ids).Scan(res.Interface()).Error if err != nil { panic("error") } minModelType := reflect.TypeOf(minModel).Elem() resValue := res.Elem() resLen := resValue.Len() ret := make(map[int]MinMapInterface, resLen) for i := 0; i < resLen; i++ { // 獲取當前下標的數據 item := resValue.Index(i).Elem() // 獲取要獲得的字段 name := item.FieldByName(minModel.TransFields()) id := item.FieldByName("ID") // 拼接返回值 setItem := reflect.New(minModelType) setItem.Elem().FieldByName("ID").SetInt(int64(id.Interface().(int))) setItem.Elem().FieldByName(minModel.TransFields()).SetString(name.Interface().(string)) // 查詢出來的內容是具體的model,這裏類型斷言轉化回去 ret[id.Interface().(int)] = setItem.Interface().(MinMapInterface) } data, _ := json.Marshal(ret) return string(data) }
修改student.go,加上獲取附加數據的方法,這裏使用了一個匿名函數,既保證了每一個model都有其獨有的參數,也保證了代碼的複用性
// AdditionalParams 附加數據參數 func (s *Student) AdditionalParams() map[string]AdditionalInfo { return map[string]AdditionalInfo{ "class": { FieldName: "ClassID", Method: func(ids []int32) string { return GetMinMap(ids, &Class{}, &ClassMin{}) }, }, } }
相應的,也要修改search.go,爲藉口添加上AdditionalParams方法,這裏直接貼上search.go的最終代碼以供比對
// SearchModel 搜索接口 type SearchModel interface { TableName() string AdditionalParams() map[string]model.AdditionalInfo } // SearchModelHandler 存儲一些查詢過程當中的必要信息 type SearchModelHandler struct { Model SearchModel ListValue reflect.Value AdditionalData string } // GetSearchModelHandler 獲取處理器 func GetSearchModelHandler(model SearchModel) *SearchModelHandler { return &SearchModelHandler{ Model: model, } } // Search 查找 func (s *SearchModelHandler) Search() interface{} { query := db.model(s.Model) itemPtrType := reflect.TypeOf(s.Model) if itemPtrType.Kind() != reflect.Ptr { itemPtrType = reflect.PtrTo(itemPtrType) } itemSlice := reflect.SliceOf(itemPtrType) res := reflect.New(itemSlice) // 這一步相當重要,雖然Scan方法接收的是一個interface{}類型,可是由於咱們這裏傳入的SearchModel,若是直接使用s.Model執行傳入會報錯 // 緣由在於這裏的Scan的interface和咱們傳入的model實現的是不一樣的接口,Scan只認識gorm包中定義的接口類型 err := query.Scan(res.Interface()).Error if err != nil { // 這裏不要學我 panic("error") } s.ListValue = res.Elem() data, _ := json.Marshal(res) ret := map[string]string { "list": string(data), "additional": s.AdditionalData, } return ret } // GetAdditionalData 獲取附加信息 func (s *SearchModelHandler) GetAdditionalData() { additionParams := s.Model.AdditionalParams() list := s.ListValue listLen := list.Len() if len(additionParams) < 1 || list.Len() < 1 { s.AdditionalData = "" return } additionalIDs := make(map[string][]int) additionalData := make(map[string]string, len(additionParams)) for i := 0; i < listLen; i++ { for key, val := range additionParams { fieldName := val.FieldName // 判斷Map中的鍵是否已存在 if _, ok := additionalIDs[key]; !ok { additionalIDs[key] = make([]int, 0, listLen) } fields := list.Index(i).Elem().FieldByName(fieldName) if !fields.IsValid() { continue } additionalIDs[key] = append(additionalIDs[key], fields.Interface().(int)) } } for k, v := range additionalIDs { additionalData[k] = additionParams[k].Method(v) } ret, _ := json.Marshal(additionalData) s.AdditionalData = string(ret) }