golang,go,博客,开源,编程
do
是 Go 语言中一个轻量级的依赖注入(Dependency Injection, DI)容器,由 samber 开发。
它基于 Go 1.18+ 泛型实现,为 Go 提供了一个类型安全的 DI 方案。
do
库的设计理念是简化服务组件之间的依赖管理,取代手工创建依赖关系的繁琐工作,使不同组件之间松散耦合、更易测试与维护。
与反射型 DI 框架不同,do
在注册和解析依赖时不使用反射,因此性能开销很小。
do.Provide
系列函数将服务构造函数注册到容器中(默认懒加载,即按需单例创建);也可以使用 ProvideTransient
注册每次调用都新建实例的工厂(瞬时模式);或使用 ProvideValue
/ProvideNamedValue
注册已经创建好的实例(急加载)。注册时可指定名称或匿名服务(推荐匿名,由框架自动命名)。do.Invoke[T](injector)
或 do.MustInvoke[T](injector)
获取指定类型的服务实例。容器会自动根据函数签名的参数解析依赖,并以依赖图的方式按顺序实例化各服务(默认单例)。服务加载顺序为调用顺序(先调用的服务会优先初始化)。do
支持生命周期钩子。服务只要实现特定接口,就会被框架在适当时机调用。比如实现 do.Healthcheckable
接口的服务可以通过 do.HealthCheck[T](injector)
或 injector.HealthCheck()
进行健康检查;实现 do.Shutdownable
接口的服务会在容器关闭时被回调,以便释放资源。容器关闭时会按照服务注册的 反初始化顺序(后注册的先关闭)依次调用这些 Shutdown
方法。Override*
)和组合(do.Package
),可以复制容器(injector.Clone()
),并提供工具函数列出已注册或已实例化的服务列表。整个库非常轻量,无外部依赖,也无需生成代码。下面给出一个简单示例,演示如何使用 do
完成依赖注入。假设有两个服务 Engine
和 Car
,其中 Car
依赖于 Engine
:
type Engine struct {
Started bool
}
// 构造函数:创建 Engine 实例
func NewEngine(i do.Injector) (*Engine, error) {
return &Engine{Started: false}, nil
}
type Car struct {
Engine *Engine
}
// 构造函数:创建 Car 实例时自动注入 Engine
func NewCar(i do.Injector) (*Car, error) {
engine := do.MustInvoke[*Engine](i) // 从容器中获取 Engine
return &Car{Engine: engine}, nil
}
func (c *Car) Start() {
c.Engine.Started = true
println("Car is running")
}
在 main
函数中创建容器、注册服务并调用:
func main() {
injector := do.New() // 创建依赖注入容器
do.Provide(injector, NewEngine) // 注册 Engine 服务(懒加载单例)
do.Provide(injector, NewCar) // 注册 Car 服务,依赖 Engine
// 获取 Car 实例(会自动创建 Car 及其依赖 Engine)
car := do.MustInvoke[*Car](injector)
car.Start() // 使用服务
injector.Shutdown() // 关闭容器,按注册顺序倒序调用可选的 Shutdown 钩子
}
以上代码中,调用 do.Provide
将构造函数注册到容器,do.MustInvoke[*Car]
会触发容器根据依赖关系创建对象并返回。运行后会输出 "Car is running"
。如果服务实现了 Shutdown()
方法(实现 do.Shutdownable
),容器在调用 injector.Shutdown()
时也会自动调用它完成清理。
使用 do
库相比手动通过 new
或自己维护全局变量的方式,有以下优势:
do
完全基于 Go 泛型和静态类型检查,不使用反射,不会产生运行时类型错误。编译期就能发现依赖缺失或类型不匹配的问题,避免了很多隐蔽错误。do
自身很轻量,没有外部依赖和代码生成工具,运行时开销低,适合小型服务、命令行工具、微服务等多种场景。对于有多个互相依赖组件、希望简化启动与测试流程的项目,do
是一个友好的选择。