golang基于泛型和反射实现依赖注入的插件库autowire的使用
Golang基于泛型和反射实现依赖注入的插件库autowire的使用
功能特性
- 在创建时自动装配/注入对象
- 易于使用(参见使用部分)
- 默认
Shared mode
,为类型只创建一个实例(此选项可配置) - 能够在构建时覆盖特定类型的对象,便于单元测试
- 无需代码生成
安装
go get github.com/tiendc/autowire
使用
基本用法
// 假设你有ServiceA和创建函数
type ServiceA interface {}
func NewServiceA(srvB ServiceB, repoX RepoX) ServiceA {
return <ServiceA-instance>
}
// 和ServiceB
type ServiceB interface {}
func NewServiceB(ctx context.Context, repoX RepoX, repoY RepoY) (ServiceB, error) {
return <ServiceB-instance>, nil
}
// 和RepoX
type RepoX interface {}
func NewRepoX(s3Client S3Client) (RepoX, error) {
return <RepoX-instance>, nil
}
// 和RepoY
type RepoY interface {}
func NewRepoY(redisClient RedisClient) RepoY {
return <RepoY-instance>
}
// 和一个结构体提供者
type ProviderStruct struct {
RedisClient RedisClient
S3Client S3Client
}
var (
// 初始化结构体值
providerStruct = &ProviderStruct{...}
// 创建容器并传入提供者
container = MustNewContainer([]any{
// 服务提供者
NewServiceA,
NewServiceB,
// 仓库提供者
NewRepoX,
NewRepoY,
// 结构体提供者(必须是指针)
providerStruct,
})
)
func main() {
// 更新结构体提供者中的一些值
providerStruct.RedisClient = newRedisClient()
providerStruct.S3Client = newS3Client()
// 创建ServiceA
serviceA, err := autowire.BuildWithCtx[ServiceA](ctx, container)
// 创建RepoX
repoX, err := autowire.Build[RepoX](container)
}
非共享模式
// 在创建容器时设置sharedMode
container = MustNewContainer([]any{
// 你的提供者
}, SetSharedMode(false))
// 为特定构建激活非共享模式
serviceA, err := Build[ServiceA](container, NonSharedMode())
覆盖特定类型的值
这在单元测试中很方便,可以只覆盖特定类型。
// 在单元测试中,你可能想用假实例覆盖一些`repos`和`clients`
// 注意:你可能需要使用`non-shared mode`来确保不使用缓存的对象
serviceA, err := Build[ServiceA](container, NonSharedMode(),
ProviderOverwrite[RepoX](fakeRepoX),
ProviderOverwrite[RepoY](fakeRepoY))
serviceA, err := Build[ServiceA](container, NonSharedMode(),
ProviderOverwrite[RedisClient](fakeRedisClient),
ProviderOverwrite[S3Client](fakeS3Client))
使用后回收内存
通常,依赖注入只用在程序的初始化阶段。然而,它会占用一些内存空间,在阶段结束后就会浪费。使用下面的技巧来回收autowire变量占用的内存。
var (
// 创建一个全局容器
container = MustNewContainer(...)
)
func autowireCleanUp() {
// 赋值为nil允许垃圾回收器回收占用的内存
container = nil
}
func main() {
// 初始化阶段
// 创建服务并使用它们
serviceA, _ := autowire.Build[ServiceA](container)
serviceB, _ := autowire.Build[ServiceB](container)
// 清理使用,确保你不再使用这些变量
autowireCleanUp()
}
贡献
- 欢迎提交pull request来添加新功能和修复bug
许可证
- MIT License
更多关于golang基于泛型和反射实现依赖注入的插件库autowire的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang基于泛型和反射实现依赖注入的插件库autowire的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
基于泛型和反射的Golang依赖注入库autowire
下面我将介绍如何使用泛型和反射在Go中实现一个简单的依赖注入库autowire,并展示其基本用法。
核心设计
package autowire
import (
"reflect"
"sync"
)
// Container 依赖注入容器
type Container struct {
instances sync.Map // 存储单例实例
providers sync.Map // 存储提供者函数
}
var defaultContainer = &Container{}
// Provide 注册一个提供者函数
func Provide[T any](provider func() T) {
defaultContainer.providers.Store(reflect.TypeOf((*T)(nil)).Elem(), provider)
}
// Inject 获取或创建实例
func Inject[T any]() T {
var zero T
t := reflect.TypeOf((*T)(nil)).Elem()
// 检查是否已有实例(单例模式)
if instance, ok := defaultContainer.instances.Load(t); ok {
return instance.(T)
}
// 查找提供者函数
provider, ok := defaultContainer.providers.Load(t)
if !ok {
return zero
}
// 调用提供者创建实例
instance := provider.(func() T)()
defaultContainer.instances.Store(t, instance)
return instance
}
使用示例
package main
import (
"fmt"
"github.com/yourname/autowire"
)
// 定义服务接口
type Logger interface {
Log(message string)
}
// 实现服务
type ConsoleLogger struct{}
func (l *ConsoleLogger) Log(message string) {
fmt.Println("[LOG]", message)
}
// 另一个服务
type Database struct {
logger Logger
}
func NewDatabase(logger Logger) *Database {
return &Database{logger: logger}
}
func (db *Database) Query(query string) {
db.logger.Log("Executing query: " + query)
// 数据库操作...
}
// 主服务
type App struct {
db *Database
logger Logger
}
func NewApp(db *Database, logger Logger) *App {
return &App{db: db, logger: logger}
}
func (a *App) Run() {
a.logger.Log("Application started")
a.db.Query("SELECT * FROM users")
}
func main() {
// 注册服务提供者
autowire.Provide(func() Logger {
return &ConsoleLogger{}
})
autowire.Provide(func() *Database {
// 这里会自动注入Logger
return NewDatabase(autowire.Inject[Logger]())
})
autowire.Provide(func() *App {
// 自动注入依赖
return NewApp(
autowire.Inject[*Database](),
autowire.Inject[Logger](),
)
})
// 获取应用实例并运行
app := autowire.Inject[*App]()
app.Run()
}
高级特性扩展
我们可以为autowire添加更多功能:
- 命名依赖:
func ProvideNamed[T any](name string, provider func() T) {
key := namedKey{typ: reflect.TypeOf((*T)(nil)).Elem(), name: name}
defaultContainer.providers.Store(key, provider)
}
func InjectNamed[T any](name string) T {
// 类似Inject但使用命名key
}
- 生命周期控制:
const (
Singleton Lifecycle = iota // 单例(默认)
Transient // 每次请求新实例
)
func ProvideWithLifecycle[T any](provider func() T, lifecycle Lifecycle) {
// 存储生命周期信息
}
- 自动装配结构体:
func Autowire[T any](target *T) error {
v := reflect.ValueOf(target).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if tag, ok := field.Tag.Lookup("autowire"); ok {
// 根据tag注入依赖
}
}
return nil
}
最佳实践
- 接口优先:依赖应该尽可能使用接口类型,便于替换实现
- 明确依赖:在构造函数中明确声明所有依赖
- 避免循环依赖:设计时要注意服务间的依赖关系
- 合理使用生命周期:数据库连接等资源密集型对象适合单例,请求相关对象适合瞬态
这个简单的autowire实现展示了Go泛型和反射在依赖注入中的应用。实际生产环境中,你可能需要考虑更复杂的场景,如错误处理、并发安全、依赖解析顺序等。对于更复杂的需求,可以考虑使用成熟的DI框架,如Google的Wire或Uber的Dig。