Golang中常常用到import来导入包,有时或许也需要思考导入包的执行顺序

1、包导入原理

Golang 包的导入原理 - 图1程序的初始化和执行都起始于main包。编译时会依次将main包中的其他包导入(如果有引用的话),当一个包被导入时,如果该包有其他包,那么依次导入其他包(类似递归),然后初始化这些包的包级常量和变量,执行init函数(如果有的话),依次类型。当main包的其他包都导入后, 初始化main包的包级常量和变量,执行init函数(如果有的话),最后执行main函数。

2、包导入的语法

2.1、导入标准库

  1. import "fmt"

2.2、非Go Module模式下导入本地包

  1. // student包
  2. package student
  3. import "fmt"
  4. func PrintlnStudent() {
  5. fmt.Println("this is student package")
  6. }
  7. /******************************************************/
  8. // main包
  9. package main
  10. // GOPATH下student所在路径
  11. import "github.com/cyj19/mytest/import-example/student"
  12. func main() {
  13. student.PrintlnStudent()
  14. }

2.3、Go Module模式下导入项目内的包

  1. // student包
  2. package student
  3. import "fmt"
  4. func PrintlnStudent() {
  5. fmt.Println("this is a student package for modules")
  6. }
  7. /*****************************************************/
  8. package main
  9. // module名+包所在目录
  10. import "github.com/cyj19/mytest/example2/student"
  11. func main() {
  12. student.PrintlnStudent()
  13. }

2.4、Go Module模式下导入项目外的本地包

非GOPATH/myexample/teacher

  1. // teacher 必须是模块module
  2. package teacher
  3. import "fmt"
  4. func PrintlnTeacher() {
  5. fmt.Println("teacher")
  6. }
  7. // mod文件
  8. module teacher
  9. go 1.17

非GOPATH/myexample/example2

  1. // 所在项目的mod文件
  2. module example2
  3. go 1.17
  4. // 使用require指令声明teacher模块的版本
  5. require (
  6. teacher v0.0.0
  7. )
  8. // 使用replace指令声明teacher模块在本地的路径
  9. replace (
  10. teacher => ../teacher
  11. )
  1. package main
  2. import "teacher"
  3. func main() {
  4. teacher.PrintlnTeacher()
  5. }

3、问题

问题描述:main包(init函数执行global中的函数)导入global包(无init函数)和server包,server包中导入了logic包,其中global包初始化读取配置文件,而logic包的包级变量的赋值使用到了配置文件中的某个字段。运行后发现logic中获取的配置文件的某个字段失败

main.go

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/cyj19/example/global"
  5. "github.com/cyj19/example/server"
  6. "log"
  7. "net/http"
  8. )
  9. func init() {
  10. global.Init()
  11. }
  12. func main() {
  13. fmt.Printf(banner+"\n", global.Addr)
  14. server.RegisterHandle()
  15. log.Fatal(http.ListenAndServe(global.Addr, nil))
  16. }

config.go

  1. package global
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/fsnotify/fsnotify"
  6. "github.com/spf13/viper"
  7. )
  8. var Addr string
  9. func Init() {
  10. initConfig()
  11. }
  12. func initConfig() {
  13. viper.SetConfigName("chatroom")
  14. viper.SetConfigType("yaml")
  15. viper.AddConfigPath(RootDir + "/config")
  16. if err := viper.ReadInConfig(); err != nil {
  17. panic(fmt.Errorf("Fatal error config file: %w \n", err))
  18. }
  19. Addr = viper.GetString("addr")
  20. }

logic.go

  1. package logic
  2. import (
  3. "container/ring"
  4. "github.com/spf13/viper"
  5. )
  6. type offlineProcessor struct {
  7. n int // 消息数量
  8. recentRing *ring.Ring // 保存所有用户最近的n条消息
  9. userRing map[string]*ring.Ring // 保存某用户离线消息
  10. }
  11. var OfflineProcessor = newOfflineProcessor()
  12. func newOfflineProcessor() *offlineProcessor {
  13. n := viper.GetInt("offline-num")
  14. return &offlineProcessor{
  15. n: n,
  16. recentRing: ring.New(n),
  17. userRing: make(map[string]*ring.Ring),
  18. }
  19. }

原因:虽然是先导入了global包,但是global包没有init函数所以只初始化了包级的常量和变量并没有执行initConfig();logic包初始化OfflineProcessor变量时执行了newOfflineProcessor函数用到viper.GetInt(“offline-num”) 但此时viper还没有读取配置文件所以获取的值为0

解决方法:global包增加init函数,执行initConfig

  1. package global
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/fsnotify/fsnotify"
  6. "github.com/spf13/viper"
  7. )
  8. var Addr string
  9. func init() {
  10. Init()
  11. }
  12. func Init() {
  13. initConfig()
  14. }
  15. func initConfig() {
  16. viper.SetConfigName("chatroom")
  17. viper.SetConfigType("yaml")
  18. viper.AddConfigPath(RootDir + "/config")
  19. if err := viper.ReadInConfig(); err != nil {
  20. panic(fmt.Errorf("Fatal error config file: %w \n", err))
  21. }
  22. Addr = viper.GetString("addr")
  23. }