欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解在Go中如何使用Viper來管理配置

 更新時間:2023年05月06日 10:29:03   作者:江湖十年  
Viper 是一個功能齊全的 Go 應用程序配置庫,支持很多場景。在本文中,我們將深入探討 Viper 的各種用法和使用場景,以幫助讀者更好地了解和使用 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)境變量時使用的前綴。BindEnvAutomaticEnv 都將使用此前綴。例如,使用 viper.SetEnvPrefix("ENV") 設置了前綴為 ENV,并且使用 viper.BindEnv("username") 綁定了環(huán)境變量,在使用 viper.Get("username") 讀取環(huán)境變量時,實際讀取的 keyENV_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、GetFloat64GetInt、GetIntSliceGetString、GetStringMap、GetStringMapString、GetStringSliceGetTime、GetDuration

  • AllSettings() map[string]interface{}:返回所有配置。根據我的經驗,如果使用環(huán)境變量指定配置,則只能獲取到通過 BindEnv 綁定的環(huán)境變量,無法獲取到通過 AutomaticEnv 綁定的環(huán)境變量。

  • IsSet(key string) bool:值得注意的是,在使用 GetGet<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)中的單一變量詳解

    這篇文章主要給大家介紹了關于Golang常見錯誤之值拷貝和for循環(huán)中單一變量的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-11-11
  • Go語言單控制器和多控制器使用詳解

    Go語言單控制器和多控制器使用詳解

    這篇文章主要為大家詳細介紹了Go語言單控制器和多控制器的使用方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • GoLang實現(xiàn)Viper庫的封裝流程詳解

    GoLang實現(xiàn)Viper庫的封裝流程詳解

    Viper是一個用于Go語言應用程序的配置管理庫,它提供了一種簡單而靈活的方式來處理應用程序的配置,支持多種格式的配置文件,這篇文章主要介紹了GoLang封裝Viper庫的流程,感興趣的同學可以參考下文
    2023-05-05
  • Golang基于泛化調用與Nacos實現(xiàn)Dubbo代理

    Golang基于泛化調用與Nacos實現(xiàn)Dubbo代理

    這篇文章主要為大家詳細介紹了Golang如何基于泛化調用與Nacos實現(xiàn)Dubbo代理,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-04-04
  • Go中Channel發(fā)送和接收操作指南

    Go中Channel發(fā)送和接收操作指南

    在golang中channel屬于較為核心的一個功能,尤其在go協(xié)程中,channel功能尤為重要,下面這篇文章主要給大家介紹了關于Go中Channel發(fā)送和接收操作的相關資料,需要的朋友可以參考下
    2021-08-08
  • GO 語言運行環(huán)境的基礎知識

    GO 語言運行環(huán)境的基礎知識

    這篇文章主要介紹了GO 語言運行環(huán)境的基礎知識的相關資料,需要的朋友可以參考下
    2022-09-09
  • Go返回int64類型字段超出javascript Number范圍的解決方法

    Go返回int64類型字段超出javascript Number范圍的解決方法

    這篇文章主要介紹了Go返回int64類型字段超出javascript Number范圍的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-07-07
  • Golang環(huán)境搭建及打包和工具鏈詳解

    Golang環(huán)境搭建及打包和工具鏈詳解

    這篇文章主要介紹了Golang環(huán)境搭建及打包和工具鏈,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Go語言的反射機制詳解

    Go語言的反射機制詳解

    本文詳細講解了Go語言的反射機制,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • golang如何實現(xiàn)三元運算符功能

    golang如何實現(xiàn)三元運算符功能

    這篇文章主要介紹了在其他一些編程語言中,如?C?語言,三元運算符是一種可以用一行代碼實現(xiàn)條件選擇的簡便方法,那么在Go語言中如何實現(xiàn)類似功能呢,下面就跟隨小編一起學習一下吧
    2024-02-02

最新評論