很多人都说init()
函数在 Go 语言中是一种神奇的存在,那么它到底神奇在哪里呢?这里就来聊一聊在 Go 语言中 init()
函数的作用以及它的执行顺序。
作用及调用时机
顾名思义,init
是英语单词"initialization"的缩写形式,意思是初始化的意思,用来执行一些初始化操作,它在入口函数main()
之前执行,并且一个包中甚至一个文件中,可以有多个init()
函数,没有参数和返回值。
话不多说,这里直接上代码验证一下:
package main
import "fmt"
var initA = printA()
func init() {
fmt.Println("run init 1")
}
func init() {
fmt.Println("run init 2")
}
func printA() bool {
fmt.Println("run a")
return true
}
func main() {
fmt.Println("run main")
}
输出:
run a
run init 1
run init 2
run main
通过这个验证的结果可以看出,init()
函数有以下这几个特征:
- 一个文件或一个包中可以有多个
init()
init()
函数没有参数也没有返回值- 不可以主动调用
init()
函数 - 在初始化变量之后,
main()
之前执行
在项目中,init()
函数可以用来初始化配置、初始化数据库、注册插件等。
单个包中 init() 的执行顺序
上面说到在一个文件或一个包中可以定义多个init()
函数,那么它们的执行顺序是怎样的呢?假设这包中有三个文件:a.go
b.go
main.go
,内容如下:
// a.go
package main
import "fmt"
func init() {
fmt.Println("run init 1 from a.go")
}
func init() {
fmt.Println("run init 2 from a.go")
}
// b.go
package main
import "fmt"
func init() {
fmt.Println("run init 1 from b.go")
}
func init() {
fmt.Println("run init 2 from b.go")
}
// main.go
package main
import "fmt"
func init() {
fmt.Println("run init 1 from main.go")
}
func init() {
fmt.Println("run init 2 from main.go")
}
func main() {
fmt.Println("run main")
}
执行后,输出内容如下:
run init 1 from a.go
run init 2 from a.go
run init 1 from b.go
run init 2 from b.go
run init 1 from main.go
run init 2 from main.go
run main
经过多次反复试验,输入的结果始终都是上面的顺序。
综上所得结论,同一个包中的不同文件的多个init()
函数的执行顺序如下:
- 包下有多个
init()
函数,按照源文件名的(ASCII)排序从前往后执行。 - 同一个文件下多个
init()
函数,按照出现顺序执行
多个包中多个 init() 函数的执行顺序
接着,我们再来看下多个包中有多个 init()
函数的情况。这里有a
和b
两个包,然后再入口文件main.go
中依赖这两个包,目录结构如下:
.
├── a
│ └── a.go
├── b
│ └── b.go
├── go.mod
└── main.go
其中,入口main.go
中的内容如下:
package main
import (
"fmt"
_ "github.com/project/demo/a"
_ "github.com/project/demo/b"
)
func init() {
fmt.Println("run init 1 from main.go")
}
func init() {
fmt.Println("run init 2 from main.go")
}
func main() {
fmt.Println("run main")
}
输出结果如下:
run init 1 from a/a.go
run init 2 from a/a.go
run init 1 from b/b.go
run init 2 from b/b.go
run init 1 from main.go
run init 2 from main.go
run main
可以看下,多个包中的多个init()
函数的执行顺序是根据被导入的顺序执行,即先导入的先执行。
如果多个包是存在依赖顺序的话,又是怎样的执行顺序呢?这里就不上代码了,直接上结论:多个包存依赖关系的包中的init()
函数是按照依赖关系从里到外
依次执行的,就是最深层的包中的init()
函数先执行。
总结
通过前面的介绍,对init()
的作用以及执行顺序有了一定的了解。
- 包下有多个
init()
函数,按照源文件名的(ASSIC)排序从前往后执行。 - 同一个文件下多个
init()
函数,按照出现顺序执行 - 多个包存依赖关系的包中的
init()
函数是按照依赖关系从里到外
依次执行的
最后,尽管init()
的执行有一定的顺序,但是在日常编码中应该尽量避免去依赖init()
的执行顺序。