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添加更多功能:

  1. 命名依赖
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
}
  1. 生命周期控制
const (
	Singleton Lifecycle = iota // 单例(默认)
	Transient                  // 每次请求新实例
)

func ProvideWithLifecycle[T any](provider func() T, lifecycle Lifecycle) {
	// 存储生命周期信息
}
  1. 自动装配结构体
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
}

最佳实践

  1. 接口优先:依赖应该尽可能使用接口类型,便于替换实现
  2. 明确依赖:在构造函数中明确声明所有依赖
  3. 避免循环依赖:设计时要注意服务间的依赖关系
  4. 合理使用生命周期:数据库连接等资源密集型对象适合单例,请求相关对象适合瞬态

这个简单的autowire实现展示了Go泛型和反射在依赖注入中的应用。实际生产环境中,你可能需要考虑更复杂的场景,如错误处理、并发安全、依赖解析顺序等。对于更复杂的需求,可以考虑使用成熟的DI框架,如Google的Wire或Uber的Dig。

回到顶部