一文詳解在Go中如何使用Viper來管理配置
Viper 是一個功能齊全的 Go 應用程序配置庫,支持很多場景。它可以處理各種類型的配置需求和格式,包括設置默認值、從多種配置文件和環(huán)境變量中讀取配置信息、實時監(jiān)視配置文件等。無論是小型應用還是大型分布式系統(tǒng),Viper 都可以提供靈活而可靠的配置管理解決方案。在本文中,我們將深入探討 Viper 的各種用法和使用場景,以幫助讀者更好地了解和使用 Viper 來管理應用程序配置。
為什么選擇 Viper
當我們在做技術選型時,肯定要知道為什么選擇某一項技術,而之所以選擇使用 Viper 來管理應用程序的配置,Viper 官方給出了如下答案:
當構建應用程序時,你不想擔心配置文件格式,只想專注于構建出色的軟件。Viper 就是為了幫助我們解決這個問題而存在的。
Viper 可以完成以下工作:
查找、加載和反序列化 JSON、TOML、YAML、HCL、INI、envfile 或 Java Properties 格式的配置文件。
為不同的配置選項設置默認值。
為通過命令行標志指定的選項設置覆蓋值。
提供別名系統(tǒng),以便輕松重命名配置項而不破壞現(xiàn)有代碼。
可以輕松區(qū)分用戶提供的命令行參數(shù)或配置文件中的值是否與默認值相同。
注:關于上面第 5 點,我個人理解的使用場景是:
先從命令行參數(shù)或配置文件中讀取配置。
可以使用
viper.IsSet(key)
方法判斷用戶是否設置了key
所對應的value
,如果設置了,可以通過viper.Get(key)
獲取值。調用
viper.SetDefault(key, default_value)
來設置默認值(默認值不會覆蓋上一步所獲取到的值)。 在第 2 步中可以拿到用戶設置的值value
,在第 3 步中可以知道默認值default_value
,這樣其實就可以判斷兩者是否相同了。
Viper 采用以下優(yōu)先級順序來加載配置,按照優(yōu)先級由高到低排序如下:
顯式調用
viper.Set
設置的配置值命令行參數(shù)
環(huán)境變量
配置文件
key/value 存儲
默認值
注意 ??:Viper 配置中的鍵不區(qū)分大小寫,如
user/User/USER
被視為是相等的key
,關于是否將其設為可選,目前還在討論中。
Viper 包中最核心的兩個功能是:如何把配置值讀入 Viper 和從 Viper 中讀取配置值,接下來我將分別介紹這兩個功能。
把配置值讀入 Viper
Viper 支持多種方式讀入配置:
設置默認配置值
從配置文件讀取配置
監(jiān)控并重新讀取配置文件
從
io.Reader
讀取配置從環(huán)境變量讀取配置
從命令行參數(shù)讀取配置
從遠程 key/value 存儲讀取配置
我們一個一個來看。
設置默認配置值
一個好的配置系統(tǒng)應該支持默認值。Viper 支持使用 viper.SetDefault(key, value)
為 key
設置默認值 value
,在沒有通過配置文件、環(huán)境變量、遠程配置或命令行標志設置 key
所對應值的情況下,這很有用。
package main import ( "fmt" "github.com/spf13/viper" ) func main() { // 設置默認配置 viper.SetDefault("username", "jianghushinian") viper.SetDefault("server", map[string]string{"ip": "127.0.0.1", "port": "8080"}) // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("Username")) // key 不區(qū)分大小寫 fmt.Printf("server: %+v\n", viper.Get("server")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
username: jianghushinian
server: map[ip:127.0.0.1 port:8080]
從配置文件讀取配置
Viper 支持從 JSON、TOML、YAML、HCL、INI、envfile 或 Java Properties 格式的配置文件中讀取配置。Viper 可以搜索多個路徑,但目前單個 Viper 實例只支持單個配置文件。Viper 不會默認配置任何搜索路徑,將默認決定留給應用程序。
主要有兩種方式來加載配置文件:
通過
viper.SetConfigFile()
指定配置文件,如果配置文件名中沒有擴展名,則需要使用viper.SetConfigType()
顯式指定配置文件的格式。通過
viper.AddConfigPath()
指定配置文件的搜索路徑中,可以通過多次調用,來設置多個配置文件搜索路徑。然后通過viper.SetConfigName()
指定不帶擴展名的配置文件,Viper 會根據所添加的路徑順序查找配置文件,如果找到就停止查找。
package main import ( "errors" "flag" "fmt" "github.com/spf13/viper" ) var ( cfg = flag.String("c", "", "config file.") ) func main() { flag.Parse() if *cfg != "" { viper.SetConfigFile(*cfg) // 指定配置文件(路徑 + 配置文件名) viper.SetConfigType("yaml") // 如果配置文件名中沒有擴展名,則需要顯式指定配置文件的格式 } else { viper.AddConfigPath(".") // 把當前目錄加入到配置文件的搜索路徑中 viper.AddConfigPath("$HOME/.config") // 可以多次調用 AddConfigPath 來設置多個配置文件搜索路徑 viper.SetConfigName("cfg") // 指定配置文件名(沒有擴展名) } // 讀取配置文件 if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { fmt.Println(errors.New("config file not found")) } else { fmt.Println(errors.New("config file was found but another error was produced")) } return } fmt.Printf("using config file: %s\n", viper.ConfigFileUsed()) // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) }
假如有如下配置文件 config.yaml
與示例程序在同一目錄中:
username: jianghushinian password: 123456 server: ip: 127.0.0.1 port: 8080
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go -c ./config.yaml
using config file: ./config.yaml
username: jianghushinian
監(jiān)控并重新讀取配置文件
Viper 支持在應用程序運行過程中實時讀取配置文件,即熱加載配置。
只需要調用 viper.WatchConfig()
即可開啟此功能。
package main import ( "fmt" "time" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() // 注冊每次配置文件發(fā)生變更后都會調用的回調函數(shù) viper.OnConfigChange(func(e fsnotify.Event) { fmt.Printf("config file changed: %s\n", e.Name) }) // 監(jiān)控并重新讀取配置文件,需要確保在調用前添加了所有的配置路徑 viper.WatchConfig() // 阻塞程序,這個過程中可以手動去修改配置文件內容,觀察程序輸出變化 time.Sleep(time.Second * 10) // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) }
值得注意的是,在調用 viper.WatchConfig()
監(jiān)控并重新讀取配置文件之前,需要確保添加了所有的配置路徑。
并且,我們還可以通過 viper.OnConfigChange()
函數(shù)注冊一個每次配置文件發(fā)生變更后都會調用的回調函數(shù)。
我們依然使用上面的 config.yaml
配置文件:
username: jianghushinian password: 123456 server: ip: 127.0.0.1 port: 8080
執(zhí)行以上示例代碼,并在程序阻塞的時候,手動修改配置文件中 username
所對應的值為 江湖十年
,可以得到如下輸出:
$ go run main.go
config file changed: config.yaml
username: 江湖十年
從 io.Reader
讀取配置
Viper 支持從任何實現(xiàn)了 io.Reader
接口的配置源中讀取配置。
package main import ( "bytes" "fmt" "github.com/spf13/viper" ) func main() { viper.SetConfigType("yaml") // 或者使用 viper.SetConfigType("YAML") var yamlExample = []byte(` username: jianghushinian password: 123456 server: ip: 127.0.0.1 port: 8080 `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) }
這里我們通過 bytes.NewBuffer()
構造了一個 bytes.Buffer
對象,它實現(xiàn)了 io.Reader
接口,所以可以直接傳遞給 viper.ReadConfig()
來從中讀取配置。
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
username: jianghushinian
從環(huán)境變量讀取配置
Viper 還支持從環(huán)境變量讀取配置,有 5 個方法可以幫助我們使用環(huán)境變量:
AutomaticEnv()
:可以綁定全部環(huán)境變量(用法上類似 flag 包的flag.Parse()
)。調用后,Viper 會自動檢測和加載所有環(huán)境變量。BindEnv(string...) : error
:綁定一個環(huán)境變量。需要一個或兩個參數(shù),第一個參數(shù)是配置項的鍵名,第二個參數(shù)是環(huán)境變量的名稱。如果未提供第二個參數(shù),則 Viper 將假定環(huán)境變量名為:環(huán)境變量前綴_鍵名
,且為全大寫形式。例如環(huán)境變量前綴為ENV
,鍵名為username
,則環(huán)境變量名為ENV_USERNAME
。當顯式提供第二個參數(shù)時,它不會自動添加前綴,也不會自動將其轉換為大寫。例如,使用viper.BindEnv("username", "username")
綁定鍵名為username
的環(huán)境變量,應該使用viper.Get("username")
讀取環(huán)境變量的值。在使用環(huán)境變量時,需要注意,每次訪問它的值時都會去環(huán)境變量中讀取。當調用
BindEnv
時,Viper 不會固定它的值。SetEnvPrefix(string)
:可以告訴 Viper 在讀取環(huán)境變量時使用的前綴。BindEnv
和AutomaticEnv
都將使用此前綴。例如,使用viper.SetEnvPrefix("ENV")
設置了前綴為ENV
,并且使用viper.BindEnv("username")
綁定了環(huán)境變量,在使用viper.Get("username")
讀取環(huán)境變量時,實際讀取的key
是ENV_USERNAME
。SetEnvKeyReplacer(string...) *strings.Replacer
:允許使用strings.Replacer
對象在一定程度上重寫環(huán)境變量的鍵名。例如,存在SERVER_IP="127.0.0.1"
環(huán)境變量,使用viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
將鍵名中的.
或-
替換成_
,則通過viper.Get("server_ip")
、viper.Get("server.ip")
、viper.Get("server-ip")
三種方式都可以讀取環(huán)境變量對應的值。AllowEmptyEnv(bool)
:當環(huán)境變量為空時(有鍵名而沒有值的情況),默認會被認為是未設置的,并且程序將回退到下一個配置來源。要將空環(huán)境變量視為已設置,可以使用此方法。
注意 ??:Viper 在讀取環(huán)境變量時,是區(qū)分大小寫的。
使用示例:
package main import ( "fmt" "strings" "github.com/spf13/viper" ) func main() { viper.SetEnvPrefix("env") // 設置讀取環(huán)境變量前綴,會自動轉為大寫 ENV viper.AllowEmptyEnv(true) // 將空環(huán)境變量視為已設置 viper.AutomaticEnv() // 可以綁定全部環(huán)境變量 viper.BindEnv("username") // 也可以單獨綁定某一個環(huán)境變量 viper.BindEnv("password") // 將鍵名中的 . 或 - 替換成 _ viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 讀取配置 fmt.Printf("username: %v\n", viper.Get("username")) fmt.Printf("password: %v\n", viper.Get("password")) fmt.Printf("server.ip: %v\n", viper.Get("server.ip")) // 讀取全部配置,只能獲取到通過 BindEnv 綁定的環(huán)境變量,無法獲取到通過 AutomaticEnv 綁定的環(huán)境變量 fmt.Println(viper.AllSettings()) }
執(zhí)行以上示例代碼得到如下輸出:
$ ENV_USERNAME=jianghushinian ENV_SERVER_IP=127.0.0.1 ENV_PASSWORD= go run main.go
username: jianghushinian
password:
server.ip: 127.0.0.1
map[password: username:jianghushinian]
從命令行參數(shù)讀取配置
Viper 支持 pflag 包(它們其實都在 spf13 倉庫下),能夠綁定命令行標志,從而讀取命令行參數(shù)。
同 BindEnv
類似,在調用綁定方法時,不會設置值,而是在每次訪問時設置。這意味著我們可以隨時綁定它,例如可以在 init()
函數(shù)中。
BindPFlag
:對于單個標志,可以調用此方法進行綁定。BindPFlags
:可以綁定一組現(xiàn)有的標志集pflag.FlagSet
。
示例程序如下:
package main import ( "fmt" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( username = pflag.StringP("username", "u", "", "help message for username") password = pflag.StringP("password", "p", "", "help message for password") ) func main() { pflag.Parse() viper.BindPFlag("username", pflag.Lookup("username")) // 綁定單個標志 viper.BindPFlags(pflag.CommandLine) // 綁定標志集 // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) fmt.Printf("password: %s\n", viper.Get("password")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go -u jianghushinian -p 123456
username: jianghushinian
password: 123456
因為 pflag 能夠兼容標準庫的 flag 包,所以我們也可以變相的讓 Viper 支持 flag。
package main import ( "flag" "fmt" "github.com/spf13/pflag" "github.com/spf13/viper" ) func main() { flag.String("username", "", "help message for username") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) // 將 flag 命令行參數(shù)注冊到 pflag pflag.Parse() viper.BindPFlags(pflag.CommandLine) // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go --username jianghushinian
username: jianghushinian
如果你不使用 flag 或 pflag,則 Viper 還提供了 Go 接口的形式來支持其他 Flags。
從遠程 key/value 存儲讀取配置
要在 Viper 中啟用遠程支持,需要匿名導入 viper/remote
包:
import _ "github.com/spf13/viper/remote"
Viper 支持 etcd、Consul 等遠程 key/value 存儲,這里以 Consul 為例進行講解。
首先需要準備 Consul 環(huán)境,最方便快捷的方式就是啟動一個 Docker 容器:
$ docker run \ -d \ -p 8500:8500 \ -p 8600:8600/udp \ --name=badger \ consul agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
Docker 容器啟動好后,瀏覽器訪問 http://localhost:8500/
,即可進入 Consul 控制臺,在 user/config
路徑下編寫 YAML 格式的配置。
使用 Viper 從 Consul 讀取配置示例代碼如下:
package main import ( "fmt" "github.com/spf13/viper" _ "github.com/spf13/viper/remote" // 必須導入,才能加載遠程 key/value 配置 ) func main() { viper.AddRemoteProvider("consul", "localhost:8500", "user/config") // 連接遠程 consul 服務 viper.SetConfigType("YAML") // 顯式設置文件格式文 YAML viper.ReadRemoteConfig() // 讀取配置值 fmt.Printf("username: %s\n", viper.Get("username")) fmt.Printf("server.ip: %s\n", viper.Get("server.ip")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
username: jianghushinian
server.ip: 127.0.0.1
筆記:如果你想停止通過 Docker 安裝的 Consul 容器,則可以執(zhí)行
docker stop badger
命令。如果需要刪除,則可以執(zhí)行docker rm badger
命令。
從 Viper 中讀取配置值
前文中我們介紹了各種將配置讀入 Viper 的技巧,現(xiàn)在該學習如何使用這些配置了。
在 Viper 中,有如下幾種方法可以獲取配置值:
Get(key string) interface{}
:獲取配置項key
所對應的值,key
不區(qū)分大小寫,返回接口類型。Get<Type>(key string) <Type>
:獲取指定類型的配置值, 可以是 Viper 支持的類型:GetBool
、GetFloat64
、GetInt
、GetIntSlice
、GetString
、GetStringMap
、GetStringMapString
、GetStringSlice
、GetTime
、GetDuration
。AllSettings() map[string]interface{}
:返回所有配置。根據我的經驗,如果使用環(huán)境變量指定配置,則只能獲取到通過BindEnv
綁定的環(huán)境變量,無法獲取到通過AutomaticEnv
綁定的環(huán)境變量。IsSet(key string) bool
:值得注意的是,在使用Get
或Get<Type>
獲取配置值,如果找不到,則每個Get
函數(shù)都會返回一個零值。為了檢查給定的鍵是否存在,可以使用IsSet
方法,存在返回true
,不存在返回false
。
訪問嵌套的鍵
有如下配置文件 config.yaml
:
username: jianghushinian password: 123456 server: ip: 127.0.0.1 port: 8080
可以通過 .
分隔符來訪問嵌套字段。
viper.Get("server.ip")
示例如下:
package main import ( "fmt" "github.com/spf13/viper" ) func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() // 讀取配置值 fmt.Printf("username: %v\n", viper.Get("username")) fmt.Printf("server: %v\n", viper.Get("server")) fmt.Printf("server.ip: %v\n", viper.Get("server.ip")) fmt.Printf("server.port: %v\n", viper.Get("server.port")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
username: jianghushinian
server: map[ip:127.0.0.1 port:8080]
server.ip: 10.0.0.1
server.port: 8080
有一種情況是,配置中本就存在著叫 server.ip
的鍵,那么它會遮蔽 server
對象下的 ip
配置項。
現(xiàn)在 config.yaml
配置如下:
username: jianghushinian password: 123456 server: ip: 127.0.0.1 port: 8080 server.ip: 10.0.0.1
示例程序如下:
package main import ( "fmt" "github.com/spf13/viper" ) func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() // 讀取配置值 fmt.Printf("username: %v\n", viper.Get("username")) fmt.Printf("server: %v\n", viper.Get("server")) fmt.Printf("server.ip: %v\n", viper.Get("server.ip")) fmt.Printf("server.port: %v\n", viper.Get("server.port")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
username: jianghushinian
server: map[ip:127.0.0.1 port:8080]
server.ip: 10.0.0.1
server.port: 8080
server.ip
打印結果為 10.0.0.1
,而不再是 server
map 中所對應的值 127.0.0.1
。
提取子樹
當使用 Viper 讀取 config.yaml
配置文件后,viper
對象就包含了所有配置,并能通過 viper.Get("server.ip")
獲取子配置。
我們可以將這份配置理解為一顆樹形結構,viper
對象就包含了這個完整的樹,可以使用如下方法獲取 server
子樹。
srvCfg := viper.Sub("server")
使用示例如下:
package main import ( "fmt" "github.com/spf13/viper" ) func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() // 獲取 server 子樹 srvCfg := viper.Sub("server") // 讀取配置值 fmt.Printf("ip: %v\n", srvCfg.Get("ip")) fmt.Printf("port: %v\n", srvCfg.Get("port")) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
ip: 127.0.0.1
port: 8080
反序列化
Viper 提供了 2 個方法進行反序列化操作,以此來實現(xiàn)將所有或特定的值解析到結構體、map 等。
Unmarshal(rawVal interface{}) : error
:反序列化所有配置項。UnmarshalKey(key string, rawVal interface{}) : error
:反序列化指定配置項。
使用示例如下:
package main import ( "fmt" "github.com/spf13/viper" ) type Config struct { Username string Password string // Viper 支持嵌套結構體 Server struct { IP string Port int } } func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() var cfg *Config if err := viper.Unmarshal(&cfg); err != nil { panic(err) } var password *string if err := viper.UnmarshalKey("password", &password); err != nil { panic(err) } fmt.Printf("cfg: %+v\n", cfg) fmt.Printf("password: %s\n", *password) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
cfg: &{Chart:{Values:map[ingress:map[annotations:map[traefik.frontend.rule.type:PathPrefix traefik.ingress.kubernetes.io/ssl-redirect:true]]]}}
注意??:Viper 在后臺使用 mapstructure 來解析值,其默認情況下使用
mapstructure
tags。當我們需要將 Viper 讀取的配置反序列到結構體中時,如果出現(xiàn)結構體字段跟配置項不匹配,則可以設置mapstructure
tags 來解決。
序列化
一個好用的配置包不僅能夠支持反序列化操作,還要支持序列化操作。Viper 支持將配置序列化成字符串,或直接序列化到文件中。
序列化成字符串
我們可以將全部配置序列化配置為 YAML 格式字符串。
package main import ( "fmt" "github.com/spf13/viper" yaml "gopkg.in/yaml.v2" ) // 序列化配置為 YAML 格式字符串 func yamlStringSettings() string { c := viper.AllSettings() // 獲取全部配置 bs, _ := yaml.Marshal(c) // 根據需求序列化成不同格式 return string(bs) } func main() { viper.SetConfigFile("./config.yaml") viper.ReadInConfig() fmt.Printf(yamlStringSettings()) }
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
password: 123456
server:
ip: 127.0.0.1
port: 8080
username: jianghushinian
寫入配置文件
Viper 還支持直接將配置序列化到文件中,提供了如下幾個方法:
WriteConfig
:將當前的viper
配置寫入預定義路徑。如果沒有預定義路徑,則會報錯。如果預定義路徑已經存在配置文件,將會被覆蓋。SafeWriteConfig
:將當前的viper
配置寫入預定義路徑。如果沒有預定義路徑,則會報錯。如果預定義路徑已經存在配置文件,不會覆蓋,會報錯。WriteConfigAs
: 將當前的viper
配置寫入給定的文件路徑。如果給定的文件路徑已經存在配置文件,將會被覆蓋。SafeWriteConfigAs
:將當前的viper
配置寫入給定的文件路徑。如果給定的文件路徑已經存在配置文件,不會覆蓋,會報錯。
使用示例:
viper.WriteConfig() // 將當前配置寫入由 `viper.AddConfigPath()` 和 `viper.SetConfigName` 設置的預定義路徑。 viper.SafeWriteConfig() viper.WriteConfigAs("/path/to/my/.config") viper.SafeWriteConfigAs("/path/to/my/.config") // 將會報錯,因為它已經被寫入了。 viper.SafeWriteConfigAs("/path/to/my/.other_config")
多實例對象
由于大多數(shù)應用程序都希望使用單個配置實例對象來管理配置,因此 viper 包默認提供了這一功能,它類似于一個單例。當我們使用 Viper 時不需要配置或初始化,Viper 實現(xiàn)了開箱即用的效果。
在上面的所有示例中,演示了如何以單例方式使用 Viper。我們還可以創(chuàng)建多個不同的 Viper 實例以供應用程序中使用,每個實例都有自己單獨的一組配置和值,并且它們可以從不同的配置文件、key/value 存儲等位置讀取配置信息。
Viper 包支持的所有功能都被鏡像為 viper
對象上的方法,這種設計思路在 Go 語言中非常常見,如標準庫中的 log 包。
多實例使用示例:
package main import ( "fmt" "github.com/spf13/viper" ) func main() { x := viper.New() y := viper.New() x.SetConfigFile("./config.yaml") x.ReadInConfig() fmt.Printf("x.username: %v\n", x.Get("username")) y.SetDefault("username", "江湖十年") fmt.Printf("y.username: %v\n", y.Get("username")) }
在這里,我創(chuàng)建了兩個 Viper 實例 x
和 y
,它們分別從配置文件讀取配置和通過默認值的方式設置配置,使用時互不影響,使用者可以自行管理它們的生命周期。
執(zhí)行以上示例代碼得到如下輸出:
$ go run main.go
x.username: jianghushinian
y.username: 江湖十年
使用建議
Viper 提供了眾多方法可以管理配置,在實際項目開發(fā)中我們可以根據需要進行使用。如果是小型項目,推薦直接使用 viper
實例管理配置。
package main import ( "fmt" "github.com/spf13/viper" ) func main() { viper.SetConfigFile("./config.yaml") if err := viper.ReadInConfig(); err != nil { panic(fmt.Errorf("read config file error: %s \n", err.Error())) } // 監(jiān)控配置文件變化 viper.WatchConfig() // use config... fmt.Println(viper.Get("username")) }
如果是中大型項目,一般都會有一個用來記錄配置的結構體,可以使用 Viper 將配置反序列化到結構體中。
package main import ( "fmt" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Config struct { Username string Password string // Viper 支持嵌套結構體 Server struct { IP string Port int } } func main() { viper.SetConfigFile("./config.yaml") if err := viper.ReadInConfig(); err != nil { panic(fmt.Errorf("read config file error: %s \n", err.Error())) } // 將配置信息反序列化到結構體中 var cfg *Config if err := viper.Unmarshal(&cfg); err != nil { panic(fmt.Errorf("unmarshal config error: %s \n", err.Error())) } // 注冊每次配置文件發(fā)生變更后都會調用的回調函數(shù) viper.OnConfigChange(func(e fsnotify.Event) { // 每次配置文件發(fā)生變化,需要重新將其反序列化到結構體中 if err := viper.Unmarshal(&cfg); err != nil { panic(fmt.Errorf("unmarshal config error: %s \n", err.Error())) } }) // 監(jiān)控配置文件變化 viper.WatchConfig() // use config... fmt.Println(cfg.Username) }
需要注意的是,直接使用 viper
實例管理配置的情況下,當我們通過 viper.WatchConfig()
監(jiān)聽了配置文件變化,如果配置變化,則變化會立刻體現(xiàn)在 viper
實例對象上,下次通過 viper.Get()
獲取的配置即為最新配置。但是在使用結構體管理配置時,viper
實例對象變化了,記錄配置的結構體 Config
是不會自動更新的,所以需要使用 viper.OnConfigChange
在回調函數(shù)中重新將變更后的配置反序列化到 Config
中。
總結
本文探討 Viper 的各種用法和使用場景,首先說明了為什么使用 Viper,它的優(yōu)勢是什么。
接著講解了 Viper 包中最核心的兩個功能:如何把配置值讀入 Viper 和從 Viper 中讀取配置值。Viper 對著兩個功能都提供了非常多的方法來支持。
然后又介紹了如何用 Viper 來管理多份配置,即使用多實例。
對于 Viper 的使用我也給出了自己的建議,針對小型項目,推薦直接使用 viper
實例管理配置,如果是中大型項目,則推薦使用結構體來管理配置。
以上就是一文詳解在Go中如何使用Viper來管理配置的詳細內容,更多關于Go Viper管理配置的資料請關注腳本之家其它相關文章!
相關文章
Golang常見錯誤之值拷貝和for循環(huán)中的單一變量詳解
這篇文章主要給大家介紹了關于Golang常見錯誤之值拷貝和for循環(huán)中單一變量的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-11-11Golang基于泛化調用與Nacos實現(xiàn)Dubbo代理
這篇文章主要為大家詳細介紹了Golang如何基于泛化調用與Nacos實現(xiàn)Dubbo代理,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-04-04Go返回int64類型字段超出javascript Number范圍的解決方法
這篇文章主要介紹了Go返回int64類型字段超出javascript Number范圍的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07