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 世界里基础的介绍. 一旦你掌握了包初始化的使用,你可能会发现对你组织项目基础结构是非常有帮助的.