go 语言中代码执行流程
说明
Go
程序是通过package
来组织的,每个可独立运行的程序都需要包含一个main
包,它在编译之后会生成可执行文件;在这个main包中必定包含一个入口函数main,这个函数既没有参数,也没有返回值。 Go 程序中通常包含:包、常量、变量、init()、main()等元素,如果同时存在多个包,包之间存在依赖关系,每个包中存在多个 init 函数,每个文件中存在一个 init 函数,那么问题来了,他们之间的执行顺序是什么样的?通过本文我们来对它们之间的执行顺序做尽可能详尽的说明。
Go 的执行过程是如下图所示的方式:
下面我们在beego
框架下,利用bee
的输出测试一下这个流程。
文件目录结构如下:
在测试过程中,我们只关心以上圈出四个文件,最外层是 mian.go
,然后同级有model
和routers
两个目录,model目录目录下有init.go
和article.go
两个文件里面都有 init
函数,routers
下router.go
有 init
函数。
下面是代码
main.go
/*
* @Description:
* @User: Vijay <1937832819@qq.com>
* @Date: 2020-10-20 22:34:11
*/
package main
import (
"blogApi/middlewares"
_ "blogApi/models"
_ "blogApi/routers"
"fmt"
"github.com/astaxie/beego"
)
var _ int = returnNum()
func init() {
fmt.Println("main的init")
// 跨域
middlewares.CorsDomain()
// 登录验证
middlewares.Init()
}
// 测试初始化变量
func returnNum() int {
fmt.Println("初始化变量")
return 1
}
func main() {
beego.Run()
}
blogApi\models\article.go
package models
// ...
func init() {
fmt.Println("article.go的init")
}
blogApi\models\init.go
package models
// ...
var db interface{} = setDb()
func setDb() interface{} {
fmt.Println("model的init.go的db初始化")
return 111
}
func init() {
fmt.Println("model的inti.go的init")
}
blogApi\routers\router.go
package routers
// ...
func init() {
fmt.Println("router的init")
}
执行
执行得到如下结果:
总结
Go
要求非常严格,不允许引用不使用的包。但是有时你引用包只是为了调用init
函数去做一些初始化工作。此时空标识符(也就是下划线)的作用就是为了解决这个问题。
在打印中可以看出执行流程如下,引入包->初始化包变量—>执行包里面的init()->mian.go中初始化变量->init()->mian();
在mian.go 中我们引入了_ "blogApi/models"
和_ "blogApi/routers"
初始化,blogApi/models
下面又有article.go
和init.go
有init
方法,init.go
里面我们还初始化了一个db
变量。可以发现:
同一个package
下,变量是自上而下去初始化的,等package
包里面的变量全部初始化完后,再自上而下执行init()
,
根据上面特性,如果我们需要在model
下初始化db
,应该在最前面的文件进行初始化,然后下面的文件都可以调用,出个馊主意,比如定义一个文件名为0init.go
的文件,里面写db
变量,试一下效果:
blogApi\models\0init.go
package models
import "fmt"
var db interface{} = 111
func init() {
fmt.Println("model下0init.go初始化db")
}
修改 blogApi\models\article.go
package models
func init() {
fmt.Printf("model的article.go中能获取到db:%v\n", db)
fmt.Println("article.go的init")
}
执行结果:
可以看出,0init.go
定义的变量,在article,go
中可以获取到,这样的话,我们就可以在0init.go
初始化数据库链接了,这样在整个model
下都能拿到数据库句柄了。
包里面的代码初始化完后,再进行本地mian.go
文件变量初始化,然后执行init
,最后才去执行mian
方法
init特性及作用
init()
函数会在每个包完成初始化后自动执行,并且执行优先级比main
函数高。
init 函数的作用:
- 对变量进行初始化
- 检查/修复程序的状态
- 注册
- 运行一次计算
init 函数的特性:
init
函数不需要传入参数,也不会返回任何值。与main
相比而言,init
没有被声明,因此也不能被引用。每个源文件只能包含一个
init
函数。