golang--深入简出,带你用golang的反射撸一个公用后台查询方法
一些基本方法
本篇不会介绍反射的基本概念和原理等,会从每个常用的方法入手,讲解一些基本和进阶用法,反射不太适合在业务层使用,因为会几何倍的降低运行速度,而且用反射做出来的程序健壮度不高,一旦一个环节没有处理好就会直接panic,影响程序的运行,但是在后台上使用还是很适合的,可以极大的降低代码量,从繁复的增删改查操作和无边的抛err(面向错误编程,太贴切了)中解脱出来。
reflect.TypeOf()
可以获取任何变量的类型对象,使用该对象可以获取变量的Name和Kind,Name代表的是变量类型的名称,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,使用该对象同样可以获取变量的Name和Kind,通过获取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)
}
golang--深入简出,带你用golang的反射撸一个公用后台查询方法的更多相关文章
- ASP.Net后台 实现先弹出对话框,再跳转到另一个网页的实现方法
解决办法如下: Response.Write("<script>alert('想在对话框中显示的内容');window.navigate(‘要转到的页面的URL’)</sc ...
- CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中
CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-10-28. 编写了个golang程序,用到了这个C ...
- Golang:手撸一个支持六种级别的日志库
Golang标准日志库提供的日志输出方法有Print.Fatal.Panic等,没有常见的Debug.Info.Error等日志级别,用起来不太顺手.这篇文章就来手撸一个自己的日志库,可以记录不同级别 ...
- golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法
golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法,查询中报了以下这个错 Scan error on column index 2: ...
- 深入简出的nginx
深入简出的nginx hosts的简单介绍 nginx的简单介绍 hosts介绍 谈到nginx我们不得不说hosts hosts的存放在C:\Windows\System32\drivers\etc ...
- Vuex 2.0 深入简出
最近面试充斥了流行框架Vue的各种问题,其中Vuex的使用就相当有吸引力.下面我就将自己深入简出的心得记录如下: 1.在vue-init webpack project (创建vue项目) 2.src ...
- OpenJDK源码研究笔记(一)-参数检查&抛出带关键错误提示信息的异常
OpenJDK源码研究笔记系列文章,是我在阅读OpenJDK7源码的过程中的一些体会.收获.看法. 把研究过程中的成长和收获一点点地整理出来,是对自己研究学习的一个小结,也有可能给学习Java的一些同 ...
- 从Golang中open的实现方式看Golang的语言设计
Golang有很多优点: 开发高效:(C语言写一个hash查找很麻烦,但是go很简单) 运行高效:(Python的hash查找好写,但比Python高效很多) 很少的系统库依赖:(环境依赖少,一般不依 ...
- Golang通过反射拼接一个结构体所有字段
golang通过反射拼接一个结构体所有字段 需求 将一个结构体所有字段以"|"连接拼接成字符串 golang 不同类型拼接成string使用Sprintf比较麻烦,如果一个结构体有 ...
随机推荐
- C# .Net Core 3.1 中关于Process.Start 启动Url链接的问题
WPF 项目迁移到.Net Core中时居然出了一堆问题...(很无语) 今天在使用的时候居然发现Process.Start居然打不开Url链接了? 报 找不到指定文件 的异常?! 一.bug重现 首 ...
- 使用VS2017进行Python代码的编写并打印出九九乘法表
我们来盘一盘怎么使用VS2017进行python代码的编写并打印出九九乘法表. 使用Visual Studio 2017进行Python编程不需要太复杂的工作,只需要vs2017安装好对Python的 ...
- 【JDK】JDK源码分析-HashMap(2)
前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...
- gcc错误[Error] ld returned 1 exit status
出现这个错误的原因是:(目前遇见两种情况了) 你的编译器正在执行刚刚的程序还没关:小黑框还在. 解决措施:关闭就好. 定义的函数和调用的函数名字不一样,也会造成产生这种错误!!!代码如下: bool ...
- 《自拍教程45》Python_adb实时监控Logcat日志
接上一篇:adb命令_一键截取logcat日志, 有一天, 系统稳定性开发负责人找到我,希望我能在跑android 系统monkey的时候, 实时监控logcat的输出,如果一旦发现"jav ...
- Docker基本概念及架构
一.Docker基本概念 Docker是一个开源的容器引擎,基于Go 语言并遵从 Apache2.0 协议开源.Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布 ...
- 小程序中内容审核功能的使用(后台使用thinkPHP5.1)
本文包含文本和图片的检测 //接收要检测的文本内容并调用检测方法 public function textCheck(Request $request){ //内容安全识别 $data['conten ...
- [Alg] 文本匹配-多模匹配-AC自动机
1. 简介 AC自动机是一种多模匹配的文本匹配算法. 如果采用naive的方法,即依次比较文本串s中是否包含模式串p1, p2,...非常耗时.考虑到这些模式串中可能具有相同子串,可以利用已经比较过的 ...
- AdFind
C++实现(未开源),用于查询域内信息 http://www.joeware.net/freetools/tools/adfind/index.htm 常用命令如下: 列出域控制器名称: AdFind ...
- Aleax prize (开放域聊天系统比赛)2018冠军论文阅读笔记
Abstract Gunrock是一种社交机器人,旨在让用户参与开放域的对话.我们使用大规模的用户交互数据来迭代地改进了我们的机器人,使其更具能力和人性化.在2018年Alexa奖的半决赛期间,我们的 ...