zoukankan      html  css  js  c++  java
  • golang--深入简出,带你用golang的反射撸一个公用后台查询方法

    一些基本方法

    本篇不会介绍反射的基本概念和原理等,会从每个常用的方法入手,讲解一些基本和进阶用法,反射不太适合在业务层使用,因为会几何倍的降低运行速度,而且用反射做出来的程序健壮度不高,一旦一个环节没有处理好就会直接panic,影响程序的运行,但是在后台上使用还是很适合的,可以极大的降低代码量,从繁复的增删改查操作和无边的抛err(面向错误编程,太贴切了)中解脱出来。

    reflect.TypeOf()

    可以获取任何变量的类型对象,使用该对象可以获取变量的NameKindName代表的是变量类型的名称,Kind代表的是变量的底层类型名称,以下是两个典型的例子。

    // 系统变量
    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()方法,后面实战部分会有详细用法。

    isNil()

    判断值对象是否为nil,只能对通道、切片、数组、map、函数、interface等使用。

    isValid()

    判断值对象是否为有效值,即非其默认0值,例如数字类型的0,字符串类型的"",在实际使用中,如果不对这些值进行处理,可能会直接panic。

    reflect.SliceOf()

    配合reflect.TypeOf返回单个类型的切片类型。

    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)
    }
    
  • 相关阅读:
    第十次作业
    第八次作业
    作业七--1
    作业五
    作业六
    作业四
    作业一
    作业三
    作业2
    jsp第一次作业
  • 原文地址:https://www.cnblogs.com/peilanluo/p/12693120.html
Copyright © 2011-2022 走看看