正文
先说一下,这里用到了很多关于反射类型的功能,可能刚开始看代码,如果对反射不熟悉的可能会不是非常清晰,但是同时也是为了更好的理解golang中的反射,同时如果后面想在代码中可以直接从我的git地址get:
go get github.com/pythonsite/config_yaml
直接上代码:
// 可以用于处理读yaml格式的配置文件,同时也可以用于理解golang中的反射
package config_yaml
import (
"strings"
"errors"
"io/ioutil"
"gopkg.in/yaml.v2"
"reflect"
"fmt"
"strconv"
)
type ConfigEngine struct {
data map[interface{}]interface{}
}
// 将ymal文件中的内容进行加载
func (c *ConfigEngine) Load (path string) error {
ext := c.guessFileType(path)
if ext == "" {
return errors.New("cant not load" + path + " config")
}
return c.loadFromYaml(path)
}
//判断配置文件名是否为yaml格式
func (c *ConfigEngine) guessFileType(path string) string {
s := strings.Split(path,".")
ext := s[len(s) - 1]
switch ext {
case "yaml","yml":
return "yaml"
}
return ""
}
// 将配置yaml文件中的进行加载
func (c *ConfigEngine) loadFromYaml(path string) error {
yamlS,readErr := ioutil.ReadFile(path)
if readErr != nil {
return readErr
}
// yaml解析的时候c.data如果没有被初始化,会自动为你做初始化
err := yaml.Unmarshal(yamlS, &c.data)
if err != nil {
return errors.New("can not parse "+ path + " config" )
}
return nil
}
// 从配置文件中获取值
func (c *ConfigEngine) Get(name string) interface{}{
path := strings.Split(name,".")
data := c.data
for key, value := range path {
v, ok := data[value]
if !ok {
break
}
if (key + 1) == len(path) {
return v
}
if reflect.TypeOf(v).String() == "map[interface {}]interface {}"{
data = v.(map[interface {}]interface {})
}
}
return nil
}
// 从配置文件中获取string类型的值
func (c *ConfigEngine) GetString(name string) string {
value := c.Get(name)
switch value:=value.(type){
case string:
return value
case bool,float64,int:
return fmt.Sprint(value)
default:
return ""
}
}
// 从配置文件中获取int类型的值
func (c *ConfigEngine) GetInt(name string) int {
value := c.Get(name)
switch value := value.(type){
case string:
i,_:= strconv.Atoi(value)
return i
case int:
return value
case bool:
if value{
return 1
}
return 0
case float64:
return int(value)
default:
return 0
}
}
// 从配置文件中获取bool类型的值
func (c *ConfigEngine) GetBool(name string) bool {
value := c.Get(name)
switch value := value.(type){
case string:
str,_:= strconv.ParseBool(value)
return str
case int:
if value != 0 {
return true
}
return false
case bool:
return value
case float64:
if value != 0.0 {
return true
}
return false
default:
return false
}
}
// 从配置文件中获取Float64类型的值
func (c *ConfigEngine) GetFloat64(name string) float64 {
value := c.Get(name)
switch value := value.(type){
case string:
str,_ := strconv.ParseFloat(value,64)
return str
case int:
return float64(value)
case bool:
if value {
return float64(1)
}
return float64(0)
case float64:
return value
default:
return float64(0)
}
}
// 从配置文件中获取Struct类型的值,这里的struct是你自己定义的根据配置文件
func (c *ConfigEngine) GetStruct(name string,s interface{}) interface{}{
d := c.Get(name)
switch d.(type){
case string:
c.setField(s,name,d)
case map[interface{}]interface{}:
c.mapToStruct(d.(map[interface{}]interface{}), s)
}
return s
}
func (c *ConfigEngine) mapToStruct(m map[interface{}]interface{},s interface{}) interface{}{
for key, value := range m {
switch key.(type) {
case string:
c.setField(s,key.(string),value)
}
}
return s
}
// 这部分代码是重点,需要多看看
func (c *ConfigEngine) setField(obj interface{},name string,value interface{}) error {
// reflect.Indirect 返回value对应的值
structValue := reflect.Indirect(reflect.ValueOf(obj))
structFieldValue := structValue.FieldByName(name)
// isValid 显示的测试一个空指针
if !structFieldValue.IsValid() {
return fmt.Errorf("No such field: %s in obj",name)
}
// CanSet判断值是否可以被更改
if !structFieldValue.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}
// 获取要更改值的类型
structFieldType := structFieldValue.Type()
val := reflect.ValueOf(value)
if structFieldType.Kind() == reflect.Struct && val.Kind() == reflect.Map {
vint := val.Interface()
switch vint.(type) {
case map[interface{}]interface{}:
for key, value := range vint.(map[interface{}]interface{}) {
c.setField(structFieldValue.Addr().Interface(), key.(string), value)
}
case map[string]interface{}:
for key, value := range vint.(map[string]interface{}) {
c.setField(structFieldValue.Addr().Interface(), key, value)
}
}
} else {
if structFieldType != val.Type() {
return errors.New("Provided value type didn't match obj field type")
}
structFieldValue.Set(val)
}
return nil
}
先写一个对上面这个包的使用例子:首先是yaml配置文件的内容,这里简单写了一些内容:
Site:
HttpPort: 8080
HttpsOn: false
Domain: "pythonsite.com"
HttpsPort: 443
Nginx:
Port: 80
LogPath: "/opt/log/nginx.log"
Path: "/opt/nginx/"
SiteName: "this is my web site"
SiteAddr: "BeiJing"
测试程序的代码为:
package main
import (
"github.com/pythonsite/config_yaml"
"fmt"
)
type SiteConfig struct {
HttpPort int
HttpsOn bool
Domain string
HttpsPort int
}
type NginxConfig struct {
Port int
LogPath string
Path string
}
func main() {
c2 := config_yaml.ConfigEngine{}
c2.Load("test.yaml")
siteConf := SiteConfig{}
res := c2.GetStruct("Site",&siteConf)
fmt.Println(res)
nginxConfig := NginxConfig{}
res2 := c2.GetStruct("Nginx",&nginxConfig)
fmt.Println(res2)
siteName := c2.GetString("SiteName")
siteAddr := c2.GetString("SiteAddr")
fmt.Println(siteName,siteAddr)
}
效果如下:
感觉挺好用哈
补充一些知识点(参考go圣经)
接口值
接口值有两个部分组成:具体的类型和该类型的值,而这两个概念被称为接口的动态类型和动态值
Go语言中,变量总是被初始化之后我们才能使用,即使接口类型也不例外
对于接口值的零值就是类型的值和值的内容都是nil
对于接口值我们是可以通过==nil 或者!=nil 的方法来判断接口值是否为空
var w io.Writer
w = os.Stdout
当通过w = os.Stdout 进行赋值的时候调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。
当赋值之后w 这个接口值的动态类型被设置为*os.Stdout指针的类型描述符,它的动态值是os.Stdout的拷贝
通常在编译期,我们不知道接口值的动态类型是什么,所以一个接口上的调用必须使用动态分配。因为
不是直接进行调用,所以编译器必须把代码生成在类型描述符的方法Write上,然后间接调用那个地址。
这个调用的接收者是一个接口动态值的拷贝,os.Stdout。效果和下面这个直接调用一样:
os.Stdout.Write([]byte("hello")) 等价于 w.Write([]byte("hello"))
反射
函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type:
t := reflect.TypeOf(3)
fmt.Println(t.String())
fmt.Println(t)
我们可以查看一下reflect.TypeOf的详细方法:
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
可以看到参数类型是一个interface{},就像上面在接口值中说的这里会将这个具体的值即我们传入的3进行一个隐式的转换会创建一个包含两个信息的接口值:3这个变量的动态类型,这里是int; 3这个变量的动态值,这里是3
reflect.ValueOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Value. 和
reflect.TypeOf 类似, reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可
以持有一个接口值
v := reflect.ValueOf(3)
fmt.Println(v)
fmt.Println(v.String())
v2 := reflect.ValueOf("abc")
fmt.Println(v2)
fmt.Println(v2.String())
这里需要注意除非 Value 持有的是字符串, 否则 String 只是返回具体的类型.Value 的 Type 方法将返回具体类型所对应的 reflect.Type: