golang专注于简洁API和灵活性的DI容器插件库HnH/di的使用
Golang 专注于简洁 API 和灵活性的 DI 容器插件库 HnH/di 的使用
依赖注入
DI 是一个专注于简洁 API 和灵活性的依赖注入库。DI 有两种顶级抽象:Container 和 Resolver。 第一个负责接受构造函数和实现,并从中创建抽象绑定。 第二个针对一个或多个容器实现不同的实现解析场景。
最初这个库受到 GoLobby Container 的很大启发,但从那时起在结构、功能和 API 上有很多不兼容的更改。
要安装 DI,只需在项目目录中运行:
go get github.com/HnH/di
容器
type Container interface {
Singleton(constructor any, opts ...Option) error
Factory(constructor any, opts ...Option) error
Implementation(implementation any, opts ...Option) error
ListBindings(reflect.Type) (map[string]Binding, error)
Reset()
}
Singleton
Singleton()
方法需要一个构造函数,该构造函数将返回抽象的实现。构造函数将被调用一次,返回的实现将始终绑定到抽象。
err = di.Singleton(func() (Abstraction, SecondAbstraction) {
return Implementation, SecondImplementation
})
// Singleton 也可以接受命名选项,意味着返回的实现将仅在提供的名称下可用
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithName("customName"))
// 如果返回多个实现,可以为每个实现提供名称
err = di.Singleton(func() (Abstraction, SecondAbstraction) {
return Implementation, SecondImplementation
}, di.WithName("customName", "secondCustomName"))
// 如果只返回一个实现,可以为其提供多个别名
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithName("customName", "secondCustomName"))
// WithFill() 选项在实例创建后立即调用 `resolver.Fill()`
err = di.Singleton(func() (Abstraction) {
return Implementation
}, di.WithFill()) // di.resolver.Fill(Implementation) 将在底层调用
Factory
Factory()
方法需要一个构造函数,该构造函数将返回一个抽象的一个实现。每次抽象解析请求时都会调用构造函数。
err = di.Factory(func() (Abstraction) {
return Implementation
})
// Factory 也可以选择性地接受命名选项
err := di.Factory(func() (Abstraction) {
return Implementation
}, di.WithName("customName"))
// 类似于 Singleton 绑定,可以提供 WithFill() 选项
err = di.Factory(func() (Abstraction) {
return Implementation
}, di.WithFill()) // di.resolver.Fill(Implementation) 将在底层调用
Implementation
Implementation()
接收准备好的实例并将其绑定到其真实类型,这意味着声明的抽象变量类型(接口)被忽略。
var circle Shape = newCircle()
err = di.Implementation(circle)
// 将返回错误 di: no binding found for di_test.Shape
var a Shape
err = di.Resolve(&a)
// 将解析 circle
var c *Circle
err = di.Resolve(&a)
// 也可以使用命名选项
err = di.Implementation(circle, di.WithName("customName"))
err = di.Resolve(&c, di.WithName("customName"))
解析器
type Resolver interface {
With(implementations ...any) Resolver
Resolve(receiver any, opts ...Option) error
Call(function any, opts ...Option) error
Fill(receiver any) error
}
With
With()
接受实例化实现列表,并尝试在解析场景中使用它们。
var circle Shape = newCircle()
err = di.Implementation(circle)
// di: no binding found for di_test.Shape
di.Call(func(s Shape) { return })
// ok
di.With(circle).Call(func(s Shape) { return }))
Resolve
Resolve()
需要一个抽象接收器(指针)并用适当的实现填充它。
var abs Abstraction
err = di.Resolve(&a)
// 也可以使用之前注册的名称进行解析
err = di.Resolve(&a, di.WithName("customName"))
Call
Call()
执行带有解析实现作为参数的函数。
err = di.Call(func(a Abstraction) {
// `a` 将是 Abstraction 的实现
})
// 返回值可以通过提供选项绑定到变量
var db Database
err = di.Call(func(a Abstraction) Database {
return &MySQL{a}
}, di.WithReturn(&db))
// db == &MySQL{a}
Fill
Fill()
方法接受一个结构体(指针)并解析其字段。
err = di.Singleton(func() Mailer { return &someMailer{} })
err = di.Singleton(func() (Database, Database) {
return &MySQL{}, &Redis{}
}, di.WithName("data", "cache"))
type App struct {
mailer Mailer `di:"type"` // 按字段类型填充 (Mailer)
data Database `di:"name"` // 按字段类型填充 (Mailer) 并要求绑定名称为字段名 (data)
cache Database `di:"name"`
inner struct {
cache Database `di:"name"`
} `di:"recursive"` // 指示 DI 递归填充结构体
another struct {
cache Database `di:"name"` // 只要 App 结构体中的外部字段没有 `di:"recursive"` 标签就不会有任何影响
}
}
var App = App{}
err = container.Fill(&myApp)
// [Typed Bindings]
// `App.mailer` 将是 Mailer 接口的实现
// [Named Bindings]
// `App.data` 将是 Database 接口的 MySQL 实现
// `App.cache` 将是 Database 接口的 Redis 实现
// `App.inner.cache` 将是 Database 接口的 Redis 实现
// `App.another` 将被忽略,因为它没有 `di` 标签
注意,默认情况下,如果无法解析任何结构体字段,Fill()
方法会返回错误。如果其中一个字段是可选的,应在 di 标签中添加 omitempty 后缀。
type App struct {
mailer Mailer `di:"type,omitempty"` // 如果未提供 Mailer,Fill 不会返回错误
}
或者,可以提供 map[string]Type 或 []Type。它将填充所有可用的提供的类型的实现。
var list []Shape
container.Fill(&list)
// []Shape{&Rectangle{}, &Circle{}}
var list map[string]Shape
container.Fill(&list)
// map[string]Shape{"square": &Rectangle{}, "rounded": &Circle{}}
提供者
提供者是为容器提供某些东西的实体的抽象。
type Provider interface {
Provide(Container) error
}
构造函数
构造函数实现了 Construct()
方法,该方法在绑定到容器时(单例情况下)或在调用工厂方法后被调用。
注意,在可以调用构造函数方法之前,必须在容器中提供 context.Context
。
type Constructor interface {
Construct(context.Context) error
}
上下文传播
通过 di.Context
抽象可以进行上下文传播。快速示例:
var container = di.NewContainer()
container.Implementation(newCircle())
var (
ctx = di.Ctx(context.Background).Put(container)
shp Shape
)
err = ctx.Resolver().Resolve(&shp) // err == nil
更多关于golang专注于简洁API和灵活性的DI容器插件库HnH/di的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang专注于简洁API和灵活性的DI容器插件库HnH/di的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
HnH/di - Go语言简洁灵活的DI容器库
HnH/di 是一个专注于简洁API和灵活性的Go语言依赖注入(DI)容器库。它提供了轻量级的依赖注入解决方案,适合中小型Go项目使用。
主要特性
- 简洁的API设计:提供直观易用的接口
- 灵活的依赖解析:支持多种依赖注册和解析方式
- 生命周期管理:支持单例和临时实例
- 轻量级:无额外依赖,核心代码精简
基本用法
安装
go get github.com/hnhcodes/di
简单示例
package main
import (
"fmt"
"github.com/hnhcodes/di"
)
type Database struct {
ConnectionString string
}
type Service struct {
DB *Database `di:"inject"`
}
func main() {
// 创建容器
container := di.New()
// 注册依赖
container.Provide(&Database{
ConnectionString: "localhost:5432",
})
// 解析依赖
var service Service
if err := container.Resolve(&service); err != nil {
panic(err)
}
fmt.Printf("Service DB connection: %s\n", service.DB.ConnectionString)
// 输出: Service DB connection: localhost:5432
}
核心功能详解
1. 依赖注册
HnH/di 提供多种依赖注册方式:
// 方式1: 直接提供实例
container.Provide(&Database{ConnectionString: "dsn"})
// 方式2: 提供构造函数
container.Provide(func() (*Database, error) {
return &Database{ConnectionString: "dsn"}, nil
})
// 方式3: 带名称的注册
container.ProvideNamed("db", &Database{ConnectionString: "dsn"})
2. 依赖解析
// 方式1: 通过结构体标签注入
type App struct {
DB *Database `di:"inject"`
}
var app App
container.Resolve(&app)
// 方式2: 直接获取实例
db, err := container.Get((*Database)(nil))
// 方式3: 按名称获取
db, err := container.GetByName("db")
3. 生命周期管理
// 默认是单例模式
container.Provide(&Database{}) // 单例
// 临时实例(每次解析都新建)
container.Provide(func() (*Database, error) {
return &Database{}, nil
}, di.WithTransient())
4. 接口绑定
type DB interface {
Query(string) error
}
type Postgres struct{}
func (p *Postgres) Query(q string) error { return nil }
// 注册实现
container.Provide(&Postgres{})
// 绑定接口
container.Bind((*DB)(nil), (*Postgres)(nil))
// 使用接口
var db DB
container.Resolve(&db)
高级用法
条件注入
type Config struct {
Env string
}
type DevService struct{}
type ProdService struct{}
container.Provide(&Config{Env: "dev"})
container.Provide(&DevService{}, di.When(
func(c *Config) bool { return c.Env == "dev" },
))
container.Provide(&ProdService{}, di.When(
func(c *Config) bool { return c.Env == "prod" },
))
循环依赖检测
HnH/di 会自动检测循环依赖并返回错误:
type A struct { B *B `di:"inject"` }
type B struct { A *A `di:"inject"` }
var a A
err := container.Resolve(&a)
// 会返回循环依赖错误
最佳实践
- 在应用启动时集中注册依赖
- 优先使用接口绑定
- 合理使用单例和临时实例
- 避免过度使用DI,保持代码可读性
与其它DI库对比
相比更复杂的DI库如uber/dig或facebook/inject,HnH/di的优势在于:
- 更简单的API
- 更少的魔法
- 更直观的错误处理
- 更轻量的实现
HnH/di非常适合需要轻量级DI解决方案的项目,特别是那些不希望引入复杂依赖管理的中小型Go应用。