文章目录
前言
一、反射基本概念
①go语言反射为何而生?
②反射弊端
③怎样使用反射机制?
一、反射使用到的库及常用函数
①用到的库
②常用的字符串处理函数
(1) 将字符串加载为固定的类型strconv.ParseBool()
(2)去除字符串首尾空格strings.TrimSpace()
(3)获取特定子串的下标
(4)将字符串转换为数值strconv.Atoi()
(5)将数值转换为字符串strconv.Itoa()
③reflect包下的常用函数
(1)reflect.TypeOf
(2)reflect.ValueOf
④反射结构体类型变量
以下操作的环境条件
①反射对象可调用的方法
(1)NumField()
(2)Field()
(3)Type()
(4)Name()
(5)Kind()
(6)Elem()
(7)NumMethod()
(8)Method()
②反射对象可调用的属性
(1)name,属性字段名
(2)type,属性类型
(3)Anonymous,属性是否是匿名字段
(4)Offset,包
(5)Index,属性索引
二、使用反射实现配置文件的读取
1.配置文件包含的内容(文件名myconfig.ini,文件名可以自己起)
①配置文件构成
②配置文件中包含的信息
2.配置文件需求分析
①配置文件作用
①配置文件实现步骤
3.代码实现(带详细注释)
总结
GO GO GO !
前言
众所周知,go语言是一门静态编程语言,变量的类型在进行程序的编写时均是写死的,没有办法在运行时进行改变,您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性,今天侧重介绍一下反射机制,带大家学习一下怎样使用反射完成配置文件的读取,期间会介绍一些字符串处理函数
一、反射基本概念
①go语言反射为何而生?
为了我们使用方便,我们可以对数据进行动态赋值,以提高代码的灵活性与可复用性 为了使我们的程序更加灵活,我们使用反射的方式将数据的类型变为动态的,如果使 用switch语句进行操作的话会使得代码十分臃肿,并且有用户自定义的数据类型,很难将类型写全
②反射弊端
使用反射,涉及到很多包,需要调用很对函数,效率会有所下降 但是使用一丢丢效率换取很大的方便我们非常乐意
③怎样使用反射机制?
一、反射使用到的库及常用函数
有官方写好的第三方包,包内有接口TypeOf与ValueOf,从接口中获取目标对象的信息 TypeOf:动态的获取从函数接口中传进去的变量的类型,如果为空则返回值为nil(获取类型对象) 可以从该方法获取的对象中拿到字段的所属类型,字段名,以及该字段是否是匿名字段等信息 还可以获取到与该字段进行绑定的tag ValueOf:获取从函数入口传进去变量的值(获取值对象) 该方法获取到的对象是对应的数据信息区域(具体的值可以通过Filed(index)直接拿到对应位置的值) FieldByName(字段名) 可以通过该函数拿到成员属性的详细信息(返回值与ValueOf一样) 注:指针类型经过以上函数拿到的还是指针类型,结构体还是结构体类型 Elem()方法: 该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)
①用到的库
import ( "errors" "fmt" //读取配置文件用到的库(文件部分后期还会进行详细介绍) "io/ioutil" //反射用到的东西一般都在这里面 "reflect" //处理字符串用到的库 "strconv" "strings" )
②常用的字符串处理函数
(1) 将字符串加载为固定的类型strconv.ParseBool()
boolV, err := strconv.ParseBool("true")//这里的ParseBool可以换成ParseInt等,你需要的函数
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
(2)去除字符串首尾空格strings.TrimSpace()
str := " 1234=56789 " str=strings.TrimSpace(str)
(3)获取特定子串的下标
str := " 1234=56789 " index:=strings.Index(str, "=5")
(4)将字符串转换为数值strconv.Atoi()
i, _ := strconv.Atoi("1433223")//array to number • 1
(5)将数值转换为字符串strconv.Itoa()
s := strconv.Itoa(10)//number to array
③reflect包下的常用函数
(1)reflect.TypeOf
参数是要进行类型获取的变量,可以是任意类型 返回值是一个 reflect.Type接口,这个接口实现了一系列方法,可以获取到传入对象的详细类型信息
(2)reflect.ValueOf
参数是要进行类型获取的变量,可以是任意类型 返回值是一个 reflect.Value接口,这个接口实现了一系列方法,可以获取到传入对象的详细值信息
代码如下(示例):
//定义一个结构体 type stu struct { id string name string age int } //对结构体进行初始化 var stu1 stu stu1.age = 20 stu1.id = "hahaha" stu1.name = "小明" sTructType := reflect.TypeOf(stu1) sTructValue := reflect.ValueOf(stu1) // 结构体值 fmt.Println("值是:", sTructValue) //类型 fmt.Println(sTructType) //打印结果 //值是: {hahaha 小明 20} //main.stu
④反射结构体类型变量
以下操作的环境条件
//结构体 type stu struct { id string name string age int } //方法 func (st stu) Speak() { } func (st stu) Run(x int) { fmt.Println("跑起来,润润润!") } func (st stu) Haha() bool { fmt.Println("你是成功的!") return true } //进行反射 var stu1 stu stu1.age = 20 stu1.id = "hahaha" stu1.name = "小明" sTructType := reflect.TypeOf(stu1) sTructValue := reflect.ValueOf(stu1)
①反射对象可调用的方法
(1)NumField()
//获取属性个数 fmt.Println(sTructType.NumField()) //打印结果 //3
(2)Field()
//获取指定位置属性的详细类型 fmt.Println(sTructType.Field(0)) //打印结果 //{id main string 0 [0] false} //以上结果含义分别为: //Type Type // field type字段名 //Tag StructTag // field tag string所在包 //Offset uintptr // offset within struct, in bytes//基类型名称 //Index []int // index sequence for Type.FieldByIndex//下标 //Anonymous bool // is an embedded field//是不是匿名字段
(3)Type()
获取结构体属性的类型
(4)Name()
// 直接获取到结构体名,想要获取包名+结构体名时直接使用sTructType fmt.Println("类型是:", sTructType.Name()) //打印结果 //类型是: stu
(5)Kind()
// 获取该类型最初的基本 fmt.Println(sTructType.Kind()) //打印结果 //struct
(6)Elem()
该方法只接受指针类型与接口类型,如果不是这两种类型就会抛出异常(相当于对指针进行取元素)
(7)NumMethod()
获取方法个数,也就是结构体实现了哪些接口
(8)Method()
// 获取结构体对应的方法 fmt.Printf("该结构体有方法%d个\n", sTructType.NumMethod()) for i := 0; i < sTructType.NumMethod(); i++ { tempMethod := sTructType.Method(i) // 获取方法的属性,方法的函数参数以及返回值(仅针对公有方法有效,无法统计私有方法) fmt.Println("第", i, "个字段对应的信息为:", tempMethod.Name, tempMethod.Type) } //打印结果 /* 该结构体有方法3个 第 0 个字段对应的信息为: Haha func(main.stu) bool 第 1 个字段对应的信息为: Run func(main.stu, int) 第 2 个字段对应的信息为: Speak func(main.stu) */
②反射对象可调用的属性
(1)name,属性字段名
(2)type,属性类型
(3)Anonymous,属性是否是匿名字段
(4)Offset,包
(5)Index,属性索引
二、使用反射实现配置文件的读取
1.配置文件包含的内容(文件名myconfig.ini,文件名可以自己起)
①配置文件构成
配置文件可以进行注释,通过#或者;实现 配置文件进行分块处理,每一块的模块名用[]包裹起来 配置文件每一个模块有许多配置,每一个配置信息由键值对组成,中间通过=或者:隔开
②配置文件中包含的信息
; MySQL配置信息 [mysql] address=127.0.0.1 port=2206 username=root userpasswd=123456 ; [] ; [ #redies配置信息 [redies] host=localhost port=6379 password=root database=100 test=false go=123 ; =false ; test=
2.配置文件需求分析
①配置文件作用
配置文件记录服务器的参数配置 比如数据库的密码端口号,用户名密码等,如果不存进配置文件的话后期维护起来十分麻烦 比如数据库密码泄露,需要更新数据库密码 所以需要将容易变更的应用配置写进配置文件 通过反射机制对其进行解析,以得到更加灵活的程序
①配置文件实现步骤
解析配置文件需要的步骤 0.需要对参数进行校验,因为经过反射需要将文件内的内容进行解析,并读取到结构体对象内 所以需要改原对象中对应的数据,所以就要传入的是指针类型,否则无法修改原来的值 检验结果需要传入的参数是结构体指针,否则返回报错 1.读取文件得到字节类型的数据,并将数据按行划分,去除注释行与空行 对1得到的结果进行迭代 ①.去除行首尾的空格,判断本行是否是模块行,是的话进去通过模块名拿到结构体,并记录下来 ②.如果是非模块名将键值进行分割(分割为key,value),结构体属性中没有该对应字段名进行忽略 只有键没有值的忽略,只有值没有键的抛异常 迭代①中记录下来的结构体中的字段,并将字段与本行的key进行比对 相同的话将对应的value给该字段(key找不到匹配字段的话忽略)
3.代码实现(带详细注释)
package main import ( "errors" "fmt" "io/ioutil" "reflect" "strconv" "strings" ) //以下的``中包含的是一种解析数据的格式,是一一对应的映射关系 //比如:Address 对应ini文件内的address字段 //在进行反射的时候将address对应的数据解析到Address字段中。 type MysqlConfig struct { Address string `ini:"address"` Port int `ini:"port"` Username string `ini:"username"` Userpasswd string `ini:"userpasswd"` } type RedisConfig struct { Host string `ini:"host"` Port int `ini:"port"` Password string `ini:"password"` Database int `ini:"database"` Test bool `ini:"test"` } type Config struct { MysqlConfig `ini:"mysql"` RedisConfig `ini:"redies"` // int `ini:"mysql"` } func (c Config) Println() { mType := reflect.TypeOf(c) mValue := reflect.ValueOf(c) for i := 0; i < mType.NumField(); i++ { fmt.Println("成员结构体属性如下:", mType.Field(i).Name) tempValue := mValue.FieldByName(mType.Field(i).Name) // tempValue := mValue.FieldByName(mType.Field(i).Name) // 获取到的是值域,可以通过该对象进行每个字段的值进行获取 tempType := tempValue.Type() // tempType := tempValue.Type() //获取到的是值域的具体类型信息 // 再次迭代取值 for j := 0; j < tempType.NumField(); j++ { fmt.Println("属性类型:", tempType.Field(j).Type, "属性名:", tempType.Field(j).Name, "属性值:", tempValue.Field(j)) } fmt.Println() } } func Loading_INI(filename string, data interface{}) error { // 获取data的类型 dataType := reflect.TypeOf(data) // 获取data对应的值 dataValue := reflect.ValueOf(data) // 判断传进来的参数是不是指针类型,并且判断一下这个指针类型指向的是不是结构体 if dataType.Kind() != reflect.Ptr || dataType.Elem().Kind() != reflect.Struct { return errors.New("the 2 args must be struct ptr") } // 读取配置文件 bytes, err := ioutil.ReadFile("myconfig.ini") if err != nil { return err } iniLines := strings.Split(string(bytes), "\r\n") // 用于暂时存储ini配置文件内的模块名对应的结构体名 var ComfigModelStructName string // 迭代每一行数据,进行对应的操作 for index, line := range iniLines { // 去除行前面的空格 line = strings.TrimSpace(line) // 判断是不是注释行与空行,是的话直接跳过本次循环 if len(line) == 0 || (line[0] == '#' || line[0] == ';') { //排除掉空行,以及注释 continue } else if line[0] == '[' { // 如果该行没有] if line[len(line)-1] != ']' { return fmt.Errorf("the '[' has nomatch ']' in %d line", index) //如果该行里面有[]却没有字段 } else if len(strings.TrimSpace(line[1:len(line)-1])) == 0 { return fmt.Errorf("the [ ] has none in %d line", index) //字段书写规范 } else { // 将配置文件内的模块名拿出来 for i := 0; i < dataType.Elem().NumField(); i++ { if line[1:len(line)-1] == string(dataType.Elem().Field(i).Tag.Get("ini")) { ComfigModelStructName = dataType.Elem().Field(i).Name } } fmt.Println("结构体", ComfigModelStructName, "正在解析!") } } else { // 将数据以等号为分隔符进行分割 tempIndex := strings.Index(line, "=") Mkey := line[0:tempIndex] Mvalue := line[tempIndex+1:] // 如果只有值没有键,那么抛出异常,只有键没有值,那么数据为默认值直跳过本次循环 if len(strings.TrimSpace(Mkey)) == 0 { return fmt.Errorf("the Key is null in %d line", index+1) } else if len(strings.TrimSpace(Mvalue)) == 0 { continue } else { // 根据对应的结构体名拿到结构体具体信息 sValue := dataValue.Elem().FieldByName(ComfigModelStructName) sType := sValue.Type() if sType.Kind() != reflect.Struct { return errors.New("the data members must be Struct") } for i := 0; i < sType.NumField(); i++ { if sType.Field(i).Tag.Get("ini") == Mkey { switch sType.Field(i).Type.Kind() { case reflect.String: sValue.Field(i).SetString(Mvalue) case reflect.Bool: mbool, err := strconv.ParseBool(Mvalue) if err != nil { return fmt.Errorf("the value must be true or false in %d line", index+1) } sValue.Field(i).SetBool(mbool) case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: mint, err := strconv.ParseInt(Mvalue, 10, 64) if err != nil { return fmt.Errorf("the value must be number in %d line", index+1) } sValue.Field(i).SetInt(mint) case reflect.Float32, reflect.Float64: mfloat, err := strconv.ParseInt(Mvalue, 10, 64) if err != nil { return fmt.Errorf("the value must be number in %d line", index+1) } sValue.Field(i).SetInt(mfloat) } } } } } } fmt.Println("所有配置项解析完毕!") return nil } /* dataType.Elem().Kind() 写法会有很大的隐患,因为如果dataType不是指针类型就会报错,Elem方法只是为了拿到指针指向的元素 if dataType.Kind() != reflect.Ptr || dataType.Elem().Kind() != reflect.Struct 不报错 因为go语言判断的时候有阻断机制,在第一个判断条件为为真时不会去判断第二个条件,所以||后面的语句没有执行,不会报错 */ func main() { var cg1 Config err := Loading_INI("myconfig.ini", &cg1) if err != nil { fmt.Println("error : ", err) } cg1.Println() } /* 打印结果 结构体 MysqlConfig 正在解析! 结构体 RedisConfig 正在解析! 所有配置项解析完毕! 成员结构体属性如下: MysqlConfig 属性类型: string 属性名: Address 属性值: 127.0.0.1 属性类型: int 属性名: Port 属性值: 2206 属性类型: string 属性名: Username 属性值: root 属性类型: string 属性名: Userpasswd 属性值: 123456 成员结构体属性如下: RedisConfig 属性类型: string 属性名: Host 属性值: localhost 属性类型: int 属性名: Port 属性值: 6379 属性类型: string 属性名: Password 属性值: root 属性类型: int 属性名: Database 属性值: 100 属性类型: bool 属性名: Test 属性值: false */
总结
以上是博主在进行Go语言反射模块学习的时候记下的笔记,Go语言反射可以极大地方便我们完成需求,这些还是远远不够的,还需要细化学习,弄清楚每一个函数是干嘛的。只有熟悉了才能使用的时候得心应手。