package main
func main() {
initMysql()
initRedis()
initElasticSearch()
initRocketMQ()
// init others
}
是否觉得这样的手动初始化依赖的第三方组件过于丑陋?
按照依赖的传递性与分层结构,这不应该也不能是顶层所应该关心的事.
当使用进行应用开发时,有些时候可能需要在项目启动的时候做一些配置上的初始化, 比如创建数据库的连接,或者加载本地文件等等.
当使用 go
开发时,这个时候就轮到 init
函数上场了.
在 go
中,init
函数十分强大, 相较于其他的编程语言所相似的特性,init
要更加容易使用得多.
init
函数在包块中使用,不管当前包被其他包导入多少次, init
函数只会执行一次.
执行一次这个特性是时候引起我们的注意. 这可以允许我们有效地配置数据库连接,注册服务中心,或者执行一系列只会做一遍的任务.
package main
import "fmt"
func main() {
fmt.Println("我是 main")
}
func init() {
fmt.Println("我是init, 我会跑在 main 前面")
}
注意到上面的例子,我们没有在程序的任何地方显示地调用 init
函数.
go
为我们隐匿地执行了处理.
输出结果应该为:
我是init, 我会跑在 main 前面
我是 main
这很6.
我们开始做些更加 cool 的事情吧.
package main
import "fmt"
var name string
func main() {
content := fmt.Sprintf("我的名字是%s", name)
fmt.Println(content)
}
func init() {
name = "焦迈奇"
}
在上面这个例子中, 可以开始发现为什么相比显式调用设置的调用, 使用 init
函数会更加的受欢迎与偏好.这让我们更加能更好分离关注点-配置与使用.
当运行上面的例子, 变量 name
已经被正确的设置了.
我们来看一下更加贴近生产环境的案例.
想像一下我们有3个不同的包在我们的应用程序中,main
, broker
与 database
或者其他.
在每个包中,我们可以指定 init
函数来执行一些数据库连接到第三方服务(比如 mysql, kafka)等任务.
不管我们调用多少次,在使用数据库资源时, 使用的都是在 init
函数中设置好的数据库连接.
note: 有一点至关重要,不要依赖于 init 函数的执行顺序.而是要设计出无关 init 执行顺序的程序系统.
对于更复杂的系统来说,在给定的一个包下,可能存在多个代码文件.
每个文件都拥有自己的 init
函数.
所以这种情况下, go
会怎样的组织他们的执行顺序呢?
当谈到初始化的顺序时,需要考虑一些事情。
Go 中的事物通常按声明顺序的顺序初始化,或者在它们可能依赖的任何变量之后初始化。
这意味着,如果你在同一个包中有 2 个文件 a.go
和 b.go
,如果 a.go
中任何东西的初始化依赖于 b.go
中的东西,
它们将首先被初始化。
举个粟子
service 包中有两个文件
- user
- book
user
package service
import "fmt"
var bookService = NewBookService()
type UserService struct {
}
func (s UserService) Borrow(bookName string) {
fmt.Println(fmt.Sprintf("教练,我想要借本书%s", bookName))
bookService.Record(bookName)
}
func init() {
fmt.Println("user service init")
}
book
package service
import "fmt"
type BookService struct {
}
func (s BookService) Record(bookName string) {
fmt.Println("记录下来..." + bookName)
}
func init() {
fmt.Println("book service init")
}
func NewBookService() BookService {
return BookService{}
}
准备 main 文件
package main
import "demo/service"
func main() {
userService := service.UserService{}
userService.Borrow("本草纲目")
}
因为 user
文件的变量 bookService
依赖于 book
文件, 所以变量 bookService
将会被首先初始化.
等到所有依赖的变量完成初始化,接着才是按顺序决定 init
函数的执行.
如果有同个文件出现多个 init
函数会发生什么?
一开始我也不相信这是真的.
但是 go
确实提供这个能力.
这些 init
函数在文件中按照各自的声明顺序被调用.
package main
import "fmt"
func main() {
// empty
}
func init(){
fmt.Println("init one")
}
func init(){
fmt.Println("init two")
}
func init(){
fmt.Println("init three")
}
输出:
init one
init two
init three
除了变量的依赖关系使得 init
函数首先执行变得不再是那么确定.
除此之外, 常量的初始化会比变量更加高级.
这里给出一张生动的图来简单了解:
本文包含了 init 世界里基础的介绍. 一旦你掌握了包初始化的使用,你可能会发现对你组织项目基础结构是非常有帮助的.