The go third-party library github.com/spf13/viper reads and injects configuration files into the structure for ease of use.
Where
viperInstance := viper.New() // viper instance viperInstance.WatchConfig() viperInstance.OnConfigChange(func(e fsnotify.Event) { log.Print("Config file updated.") viperLoadConf(viperInstance) // Method of loading configuration })
Configurable hot updates take effect without restarting the project's new configuration (there are more than one way to achieve hot loading, such as when the file was last modified).
Why do you write that?Why does this work immediately?Take a look at how viper achieves hot updates based on these two questions.
The core of the above code is in two places: the WatchConfig() method and the OnConfigChange() method.The WatchConfig() method is used to turn on event monitoring, determine if the file can be read properly after the user manipulates it, and inject the content into the config field of the viper instance. First, look at the WatchConfig() method:
func (v *Viper) WatchConfig() { go func() { // Create a new monitoring handler, open a protocol to start waiting for events // Read from I/O completion port and inject events into Event object: Watcher.Events watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way filename, err := v.getConfigFile() if err != nil { log.Println("error:", err) return } configFile := filepath.Clean(filename) //Configuration file E:\etc\bizsvc\config.yml configDir, _ := filepath.Split(configFile) // E:\etc\bizsvc\ done := make(chan bool) go func() { for { select { // The event object read has two properties, Name is E:\etc\bizsvc\config.yml, Op is write (operation on file) case event := <-watcher.Events: // Clear the inside., and the elements before it, and clear the current path., to determine if the file for the operation is a configFile if filepath.Clean(event.Name) == configFile { // If the file is created or written if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { err := v.ReadInConfig() if err != nil { log.Println("error:", err) } v.onConfigChange(event) } } case err := <-watcher.Errors: // Errors will be printed log.Println("error:", err) } } }() watcher.Add(configDir) <-done }() }
Among them, fsnotify is a third-party library used to monitor directories and files; watcher, err: = fsnotify.NewWatcher() is used to create a new monitoring handler, which opens a protocol to start waiting for events to be read, completes the port read task from I/O, and injects events into the Event object, Watcher.Events;
After executing v.ReadInConfig(), the contents of the configuration file are re-read into the viper instance, as follows:
After v.ReadInConfig() is executed, the content of the config field is the latest content modified by the user.
The onConfigChange of this line v.onConfigChange(event) is a property of the core structure Viper, and the type is func:
type Viper struct { // Delimiter that separates a list of keys // used to access a nested value in one go keyDelim string // A set of paths to look for the config file in configPaths []string // The filesystem to read config from. fs afero.Fs // A set of remote providers to search for the configuration remoteProviders []*defaultRemoteProvider // Name of file to look for inside the path configName string configFile string configType string envPrefix string automaticEnvApplied bool envKeyReplacer *strings.Replacer config map[string]interface{} override map[string]interface{} defaults map[string]interface{} kvstore map[string]interface{} pflags map[string]FlagValue env map[string]string aliases map[string]string typeByDefValue bool // Store read properties on the object so that we can write back in order with comments. // This will only be used if the configuration read is a properties file. properties *properties.Properties onConfigChange func(fsnotify.Event) }
It is used to pass in this event to execute the function you write.Why do changes take effect immediately?I believe the second question has been solved.
Next, let's see how OnConfigChange(func(e fsnotify.Event) {...}) works:
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { v.onConfigChange = run }
The method parameter is a function of type func (in fsnotify.Event){}, which means that the developer needs to put his own execution logic into this func and execute the function you write when he listens to the event, so he can write as follows:
viperInstance.OnConfigChange(func(e fsnotify.Event) { log.Print("Config file updated.") viperLoadConf(viperInstance) // The viperLoadConf function is the logic to inject the latest configuration into a custom structure object })
The parameters of the OnConfigChange method are assigned to the parameter run and passed to the onConfigChange property of the viper instance to execute the function with the v.onConfigChange(event) in the WatchConfig() method.
At this point, the first question has been solved.