go官方提供了一个wire模块https://github.com/google/wire来完成依赖注入
wire 定义了两个概念:provider 和 injector
- Provider。Provider 往往是一些简单的工厂函数,如:
func NewPerson() *Person {
...
}
func NewStudent(p Person) *Student{
...
}
func NewCollegeStudent(s Student) *CollegeStudent {
...
}
这里Student实例依赖Person实例,CollegeStudent实例依赖Student实例
- Injector。Injectors are generated functions that call providers in dependency order. You write the injector’s signature, including any needed inputs as arguments, and insert a call to wire.Build with the list of providers or provider sets that are needed to construct the end result. Injector其实还是个工厂函数,复杂的点在于函数内部需要按照依赖关系编排代码,从而保证最后能够正确返回目标实例
这个injector原本是程序员手敲的,如:
func GetCollegeStudent() *CollegeStudent {
p := NewPerson()
s := NewStudent(p)
cs := NewCollegeStudent(s)
return cs
}
wire的作用就是,把一系列的依赖顺序给隐藏了,你只需把所有的provider传入wire.Build,无需考虑provider的顺序
func GetCollegeStudent() *CollegeStudent {
panic(wire.Build(
NewCollegeStudent,
NewStudent,
NewPerson,
))
}
go generate
因此,google的wire实际上就是个代码生成工具,用来解决依赖问题的代码生成工具
self-develop wire本文的重点是,自研的基于图的后序遍历的依赖注入框架:wire
wire——延迟package的初始化
一般而言,一个golang项目(或者说一个module)的main.go会import项目内(或者说module内)的其他package,也就是main.go会对其他package产生依赖,而这些其他的package又可能import更多其他的package,从而形成了比较复杂的引用关系图
依赖的抽象:后序遍历 与 前序遍历
上层能力依赖下层能力的初始化,因此最底层的依赖应该最先初始化,初始化的时候应该遵循图的后序遍历;在进程出现问题的时候又应该最后一个停止,停止的时候遵循图的前序遍历
importinit
但是,项目里经常存在一个config包,通常是最底层的依赖包。它的初始化必须依赖于main.go读入配置文件路径,而按上面的设想,main.go又依赖于config的初始化,两者相互依赖对方的初始化,死锁,矛盾。因此,当项目内的部分包的初始化依赖于外部参数时,init方法完成初始化就行不通
那么,我们可以延迟每个package的初始化,在保证各种外部参数都准备好之后再行“init”。换句话说,借助golang import本身后序遍历的顺序来保存包的初始化顺序(越底层的包越先初始化),这样在main import 完毕后,我们获得了整个项目的上下游依赖顺序。然后,我们显式地加载配置文件;然后,我们再依据拿到的依赖顺序,显式地初始化项目中各个package的依赖:
wireService
wire.Append()wire
importinitmaininitwire
wire.Init()Service.Init()