golang--深刻簡出,帶你用golang的反射擼一個公用後臺查詢方法

一些基本方法

本篇不會介紹反射的基本概念和原理等,會從每一個經常使用的方法入手,講解一些基本和進階用法,反射不太適合在業務層使用,由於會幾何倍的下降運行速度,並且用反射作出來的程序健壯度不高,一旦一個環節沒有處理好就會直接panic,影響程序的運行,可是在後臺上使用仍是很適合的,能夠極大的下降代碼量,從繁複的增刪改查操做和無邊的拋err(面向錯誤編程,太貼切了)中解脫出來。git

reflect.TypeOf()

能夠獲取任何變量的類型對象,使用該對象能夠獲取變量的NameKindName表明的是變量類型的名稱,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.ValueOf()

能夠獲取任意變量的值對象,它的類型是reflect.Value,使用該對象一樣能夠獲取變量的NameKind,經過獲取Kind可使用類型斷言獲取變量的值。編程

這裏的reflect.ValueOf其實做用不大,在實際應用場景中多先使用reflect.ValueOf獲取變量的reflect.Value而後接Interface()方法把變量轉化爲Interface{}類型,獲取reflect.Value的方法多采用reflect.TypeOf()reflect.New()方法,後面實戰部分會有詳細用法。json

isNil()

判斷值對象是否爲nil,只能對通道、切片、數組、map、函數、interface等使用。api

isValid()

判斷值對象是否爲有效值,即非其默認0值,例如數字類型的0,字符串類型的"",在實際使用中,若是不對這些值進行處理,可能會直接panic。數組

reflect.SliceOf()

配合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.New()

配合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

reflect.PtrTo()

返回值對象的指針。

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()

返回結構體的字段數量,NumField()使用的對象必須是結構體,不然會panic。

type Student struct {
  Name string
  Age  int
}

a := &Student{
  Name: "張三",
  Age:  18,
}
reflectValue := reflect.ValueOf(a)
fmt.Println(reflectValue.Elem().NumField())	// 2

Field()

經過字段的索引獲取字段的值,從0開始,順序參照結構體定義時的由上到下的順序。

a := &Student{
  Name: "張三",
  Age:  18,
}
reflectValue := reflect.ValueOf(a)
for i := 0; i < reflectValue.Elem().NumField(); i++ {
  fmt.Println(reflectValue.Elem().Field(i))
}

FieldByName()

經過字段名稱獲取字段的值。

a := &Student{
  Name: "張三",
  Age:  18,
}
reflectValue := reflect.ValueOf(a)
fmt.Println(reflectValue.Elem().FieldByName("Name"))	// 張三

NumMethod()

返回結構體的方法數量。

FieldByNameFunc()

根據傳入的匿名函數返回對應名稱的方法。

Method()

直接經過方法的索引,返回對應的方法。

MethodByName()

經過方法名稱返回對應的方法。

以上四個方法相關的函數就不放例子了,經過對應的函數獲取到方法後,使用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)
}
相關文章
相關標籤/搜索