前言
go
语言中有一个非常神奇的函数 init
,它可以在所有程序执行开始前被执行,并且每个 package
下面可以存在多个 init
函数,我们一起来看看这个奇怪的 init
函数。
init 特性
init
函数在main
函数之前执行,并且是自动执行;- 每个
package
中可以存在多个init
函数; - 每个
package
中的源文件也可以存在多个init
函数; init
函数没有输入参数,返回值,也没有声明,无法引用;- 不同的
package
中的init
函数按照包导入的依赖关系决定执行顺序; - 无论包被导入多少次,
init
函数只会执行一次。
init 的执行顺序
这张图清晰反应了 init
函数的加载顺序:
- 优先级最高的是
package
加载,先层层递归进行包加载 - 每个包中的加载顺序是:
const
->var
->init
变量的初始化顺序
针对变量的初始化顺序,GO
官方文档有一个例子
- 这个例子的初始化顺序:
d
->b
->c
->a
- 变量的初始化顺序是按照出现的顺序进行先后加载的
- 如果某个变量需要依赖其他变量,则被依赖的变量先初始化
package 中多个 init 的执行顺序
GO
官方文档对这个有专门的说明
- 如果当前包下有多个
init
函数,首先按照源文件名的字典序从前往后执行 - 若一个文件中出现多个
init
函数,则按照出现顺序从前往后进行执行
加载顺序总结
- 从当前包开始,如果当前包
import
了多个依赖包, - 先加载依赖包,层层递归初始化各个包,
- 在每一个包中,按照源文件的字典序从前往后执行,
- 每一个源文件中, 优先初始化常量,变量,最后是
init
函数, - 当出现多个
init
函数时,则按照出现的顺序从前往后一次执行,
- 每一个源文件中, 优先初始化常量,变量,最后是
- 每一个包都初始化完成后,递归返回
- 在每一个包中,按照源文件的字典序从前往后执行,
- 初始化当前包。
init 的使用场景
- 服务注册
- 数据库,缓存等中间件的初始化连接
init 注意事项
- 开发时尽量不要依赖
init
的顺序, - 复杂的逻辑不要使用
init
函数, init
函数不能在代码中被显式调用,不能被引用,- 导入包不要出现循环依赖,
- 导入包仅仅想使用这个包的
init
,不使用其他方法,可以加上下划线_
, - 例如:
import _ "cumsuter_package"
, init
不应依赖 main函数里面创建的变量,因为init
先于main
执行。