先說一下,這裡用到了很多關於反射類型的功能,可能剛開始看代碼,如果對反射不熟悉的可能會不是非常清晰,但是同時也是為了更好的理解golang中的反射,同時如果後面想在代碼中可以直接從我的git地址get:go get github.com/pythonsite/config_yaml直接上代碼: 先寫 ...
先說一下,這裡用到了很多關於反射類型的功能,可能剛開始看代碼,如果對反射不熟悉的可能會不是非常清晰,但是同時也是為了更好的理解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:
t = v.Type()
fmt.Println(t)
fmt.Println(t.String())
當然我們也可以逆操作回去reflect.Value.Interface,然後通過斷言的方式實現
一個 reflect.Value 和 interface{} 都能保存任意的值. 所不同的是, 一個空的介面隱藏了值對應的 表示方式和所有的公開的方法, 因此只有我們知道具體的動態類型才能使用類型斷言來訪問內部的值, 對於內部值並沒有特別可做的事情. 相比之下, 一個 Value 則有很多方法來檢查其內容, 無論它的具體類型是什麼
使用 reflect.Value 的 Kind
kinds類型卻是有限的: Bool, String 和 所有數字類型的基礎類型; Array 和 Struct 對應的聚合 類型; Chan, Func, Ptr, Slice, 和 Map 對應的引用類似; 介面類型; 還有表示空值的無效類型. (空 的 reflect.Value 對應 Invalid 無效類型.)
通過reflect.Value修改值
有一些reflect.Values是可取地址的;其它一些則不可以,例子如下:
package main import ( "reflect" "fmt" ) func main() { x := 2 a := reflect.ValueOf(2) b := reflect.ValueOf(x) c := reflect.ValueOf(&x) d := c.Elem() fmt.Println(a.CanAddr()) // false fmt.Println(b.CanAddr()) // false fmt.Println(c.CanAddr()) // false fmt.Println(d.CanAddr()) // true }
所有通過reflect.ValueOf(x)返回的 reflect.Value都是不可取地址的。但是對於d,它是c的解引用方式生成的,指向另一個變數,因此是可 取地址的。我們可以通過調用reflect.ValueOf(&x).Elem(),來獲取任意變數x對應的可取地址的 Value。
要從變數對應的可取地址的reflect.Value來訪問變數需要三個步驟。第一步是調用Addr()方法,它返回 一個Value,裡面保存了指向變數的指針。然後是在Value上調用Interface()方法,也就是返回一個 interface{},裡面通用包含指向變數的指針。最後,如果我們知道變數的類型,我們可以使用類型的斷 言機制將得到的interface{}類型的介面強制環為普通的類型指針。這樣我們就可以通過這個普通指針來 更新變數了:
package main import ( "reflect" "fmt" ) func main() { x := 2 d := reflect.ValueOf(&x).Elem() px := d.Addr().Interface().(*int) *px = 3 fmt.Print(x) }
當然這裡也可以通過另外一種方法更改:
package main import ( "reflect" "fmt" ) func main() { x := 2 d := reflect.ValueOf(&x).Elem() //px := d.Addr().Interface().(*int) //*px = 3 d.Set(reflect.ValueOf(4)) fmt.Print(x) }
但是直接Set是會報錯的,因為這裡我們正好用的賦值也是int類型,如果我們用字元串的肯定就panic了