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": ""}, }, } }