config模塊參考了database/sql中實現模式,接口和實現分離,本教程選取ini格式配置的實現,以及分析beego和config模塊的集成方式,來實現下面三個目標。
config在beego下config目錄,本文中分析如下兩個文件。css
圖1-1mysql
config中定義了兩個接口,Config和Configer。先簡單描述下,詳情見下文,Config負責文件解析並存儲。Configer是對存儲數據的操做。
圖1-2git
從config.go註釋中,咱們看到config的使用demogithub
// Usage: // import "github.com/astaxie/beego/config" //Examples. // // cnf, err := config.NewConfig("ini", "config.conf") /
看一下NewConfig函數sql
func NewConfig(adapterName, filename string) (Configer, error) { adapter, ok := adapters[adapterName] return adapter.Parse(filename) }
會調用到上圖中Parse函數,因爲咱們做用的ini配置,咱們接下來看ini中的具體實現了。cookie
appname = beepkg httpaddr = "127.0.0.1" ; http port httpport = 9090 [mysql] mysqluser = "root" mysqlpass = "rootpass"
節用方括號括起來,單獨佔一行,例如:session
[section]app
鍵(key)又名屬性(property),單獨佔一行用等號鏈接鍵名和鍵值,例如:ide
name=value函數
註釋使用英文分號(;)開頭,單獨佔一行。在分號後面的文字,直到該行結尾都所有爲註釋,例如:
; comment text
咱們看到Parse會最終調用parseData.
func (ini *IniConfig) Parse(name string) (Configer, error) { return ini.parseFile(name) } func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { data, err := ioutil.ReadFile(name) return ini.parseData(filepath.Dir(name), data) }
分析下parseData
1,讀取section,若是讀取不到,做用默認section的name.
2, 讀取行,按照=分割爲兩個值,key=>value
3, 賦值cfg.data[section][key]=value
數據會存儲到map中
圖1-3
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) { cfg := &IniConfigContainer{ ... } cfg.Lock() defer cfg.Unlock() var comment bytes.Buffer buf := bufio.NewReader(bytes.NewBuffer(data)) section := defaultSection tmpBuf := bytes.NewBuffer(nil) for { tmpBuf.Reset() shouldBreak := false //讀取一行 .... line := tmpBuf.Bytes() line = bytes.TrimSpace(line) //處理註釋,忽略 ... //讀取section名稱 if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) { section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive if comment.Len() > 0 { cfg.sectionComment[section] = comment.String() comment.Reset() } if _, ok := cfg.data[section]; !ok { cfg.data[section] = make(map[string]string) } continue } //默認section if _, ok := cfg.data[section]; !ok { cfg.data[section] = make(map[string]string) } keyValue := bytes.SplitN(line, bEqual, 2) key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive key = strings.ToLower(key) //include 忽略 ... val := bytes.TrimSpace(keyValue[1]) .... cfg.data[section][key] = ExpandValueEnv(string(val)) .... } return cfg, nil }
如何讀取數據呢,Parse會返回實現Configer的實例,見上圖1-2,咱們分析下String方法。
func (c *IniConfigContainer) String(key string) string { return c.getdata(key) }
看一下getdata方法,若是不指定section,去default取,指定,則從傳入的section取。
func (c *IniConfigContainer) getdata(key string) string { if len(key) == 0 { return "" } c.RLock() defer c.RUnlock() var ( section, k string sectionKey = strings.Split(strings.ToLower(key), "::") ) if len(sectionKey) >= 2 { section = sectionKey[0] k = sectionKey[1] } else { section = defaultSection k = sectionKey[0] } if v, ok := c.data[section]; ok { if vv, ok := v[k]; ok { return vv } } return "" }
好了,到如今已經分析完config模塊,接下來看下beego如何使用config模塊的。
在beego下的config.go中(不是config/config.go),咱們看下init方法
func init() { //beego默認配置 BConfig = newBConfig() ... if err = parseConfig(appConfigPath); err != nil { panic(err) } }
看下parseConfig
func parseConfig(appConfigPath string) (err error) { //前文分析的config模塊 AppConfig, err = newAppConfig(appConfigProvider, appConfigPath) return assignConfig(AppConfig) }
看一下 assignConfig
func assignConfig(ac config.Configer) error { for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} { assignSingleConfig(i, ac) } ... }
最後了,看下assignSingleConfig,是使用reflect實現的賦值。這裏會優先使用文件中的配置,文件中沒纔會用默認配置。
func assignSingleConfig(p interface{}, ac config.Configer) { pt := reflect.TypeOf(p) ... pt = pt.Elem() .... pv := reflect.ValueOf(p).Elem() for i := 0; i < pt.NumField(); i++ { pf := pv.Field(i) if !pf.CanSet() { continue } name := pt.Field(i).Name switch pf.Kind() { case reflect.String: pf.SetString(ac.DefaultString(name, pf.String())) ... } } }
看一下beego的默認配置吧
func newBConfig() *Config { return &Config{ AppName: "beego", RunMode: PROD, RouterCaseSensitive: true, ServerName: "beegoServer:" + VERSION, RecoverPanic: true, RecoverFunc: recoverPanic, CopyRequestBody: false, EnableGzip: false, MaxMemory: 1 << 26, //64MB EnableErrorsShow: true, EnableErrorsRender: true, Listen: Listen{ Graceful: false, ServerTimeOut: 0, ListenTCP4: false, EnableHTTP: true, AutoTLS: false, Domains: []string{}, TLSCacheDir: ".", HTTPAddr: "", HTTPPort: 8080, EnableHTTPS: false, HTTPSAddr: "", HTTPSPort: 10443, HTTPSCertFile: "", HTTPSKeyFile: "", EnableAdmin: false, AdminAddr: "", AdminPort: 8088, EnableFcgi: false, EnableStdIo: false, }, WebConfig: WebConfig{ AutoRender: true, EnableDocs: false, FlashName: "BEEGO_FLASH", FlashSeparator: "BEEGOFLASH", DirectoryIndex: false, StaticDir: map[string]string{"/static": "static"}, StaticExtensionsToGzip: []string{".css", ".js"}, TemplateLeft: "{{", TemplateRight: "}}", ViewsPath: "views", EnableXSRF: false, XSRFKey: "beegoxsrf", XSRFExpire: 0, Session: SessionConfig{ SessionOn: false, SessionProvider: "memory", SessionName: "beegosessionID", SessionGCMaxLifetime: 3600, SessionProviderConfig: "", SessionDisableHTTPOnly: false, SessionCookieLifeTime: 0, //set cookie default is the browser life SessionAutoSetCookie: true, SessionDomain: "", SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers SessionNameInHTTPHeader: "Beegosessionid", SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params }, }, Log: LogConfig{ AccessLogs: false, EnableStaticLogs: false, AccessLogsFormat: "APACHE_FORMAT", FileLineNum: true, Outputs: map[string]string{"console": ""}, }, } }