di

package
v1.0.0-alpha.25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 4, 2026 License: MIT Imports: 6 Imported by: 0

README

依赖注入 (DI)

← 返回主目录

CSGO 的依赖注入系统提供了完整的服务容器和依赖管理功能,支持自动依赖解析、类型安全的服务注册和获取。

特性

  • ✅ 类型安全的服务注册和解析
  • ✅ 自动依赖注入(构造函数注入)
  • ✅ Singleton 生命周期管理
  • ✅ 泛型 API(Get、GetOr、GetAll、TryGet)
  • ✅ 命名服务支持
  • ✅ 实例注册
  • ✅ 条件注册(TryAdd)
  • ✅ 资源自动释放(IDisposable)

快速开始

1. 基本使用
package main

import (
    "github.com/gocrud/csgo/di"
    "github.com/gocrud/csgo/web"
)

// 定义服务
type UserService struct {
    repo *UserRepository
}

func NewUserService(repo *UserRepository) *UserService {
    return &UserService{repo: repo}
}

type UserRepository struct{}

func NewUserRepository() *UserRepository {
    return &UserRepository{}
}

func main() {
    // 创建应用构建器
    builder := web.CreateBuilder()
    
    // 注册服务(自动依赖注入)
    builder.Services.Add(NewUserRepository)
    builder.Services.Add(NewUserService)  // 自动注入 UserRepository
    
    app := builder.Build()
    
    // 使用服务
    app.MapGet("/users", func() string {
        userService := di.Get[*UserService](app.Services)
        return "Hello from UserService"
    })
    
    app.Run()
}
2. 在控制器中使用
type UserController struct {
    userService *UserService
    logger      logging.ILogger
}

func NewUserController(
    userService *UserService,
    loggerFactory logging.ILoggerFactory,
) *UserController {
    return &UserController{
        userService: userService,
        logger:      logging.GetLogger[UserController](loggerFactory),
    }
}

// 注册控制器
web.AddController(builder.Services, NewUserController)

核心概念

IServiceCollection

服务集合接口,用于注册服务。所有服务注册都通过此接口完成。

type IServiceCollection interface {
    // 注册单例服务
    Add(constructor interface{}) IServiceCollection
    
    // 注册单例实例
    AddInstance(instance interface{}) IServiceCollection
    
    // 注册命名服务
    AddNamed(name string, constructor interface{}) IServiceCollection
    
    // 条件注册(如果不存在才注册)
    TryAdd(constructor interface{}) IServiceCollection
    
    // 注册后台服务
    AddHostedService(constructor interface{}) IServiceCollection
}
IServiceProvider

服务提供者接口,用于解析和获取服务。

type IServiceProvider interface {
    // 获取服务(指针填充方式)
    Get(target interface{})
    
    // 获取命名服务
    GetNamed(target interface{}, serviceKey string)
    
    // 释放资源
    Dispose() error
}
ServiceLifetime

服务生命周期枚举。当前版本支持 Singleton 生命周期。

const (
    Singleton ServiceLifetime = iota  // 单例模式
)

Singleton 生命周期

什么是 Singleton?

Singleton(单例)生命周期表示服务在整个应用程序生命周期内只创建一次实例,所有请求共享同一个实例。

特点
  • 全局唯一:整个应用只有一个实例
  • 首次创建:第一次请求时创建,之后复用
  • 线程安全:框架保证创建过程的线程安全
  • 生命周期长:随应用启动而创建,随应用关闭而销毁
适用场景

推荐使用 Singleton 的场景:

  1. 无状态服务:不保存请求相关状态的服务

    type EmailService struct{}
    
    func (s *EmailService) SendEmail(to, subject, body string) error {
        // 无状态操作,可以安全地共享
        return nil
    }
    
  2. 配置对象:应用配置、常量数据

    type AppConfig struct {
        Port     int
        Database string
    }
    
  3. 资源密集型对象:数据库连接池、HTTP 客户端

    type DatabaseConnection struct {
        pool *sql.DB
    }
    
  4. 缓存服务:内存缓存、分布式缓存客户端

    type CacheService struct {
        cache map[string]interface{}
        mu    sync.RWMutex
    }
    

不推荐使用 Singleton 的场景:

  1. 有状态服务:保存用户会话、请求上下文等
  2. 请求相关数据:需要按请求隔离的数据
  3. 一次性使用对象:每次使用都需要新实例的对象
线程安全

使用 Singleton 服务时,如果服务有可变状态,必须确保线程安全:

type CounterService struct {
    count int64
}

func (s *CounterService) Increment() int64 {
    // ❌ 不安全:并发访问会有竞态条件
    s.count++
    return s.count
}

// ✅ 安全:使用原子操作
func (s *CounterService) IncrementSafe() int64 {
    return atomic.AddInt64(&s.count, 1)
}

服务注册

Add - 注册单例服务

使用构造函数注册服务,支持自动依赖注入。

// 无依赖的服务
builder.Services.Add(NewUserRepository)

// 有依赖的服务(自动注入依赖)
builder.Services.Add(NewUserService)  // 自动注入 UserRepository

// 构造函数可以返回 (service, error)
builder.Services.Add(func() (*DatabaseConnection, error) {
    db, err := sql.Open("postgres", "...")
    if err != nil {
        return nil, err
    }
    return &DatabaseConnection{db: db}, nil
})
AddInstance - 注册实例

注册已创建的实例对象。

// 注册配置实例
config := &AppConfig{
    Port:     8080,
    Database: "postgres://...",
}
builder.Services.AddInstance(config)

// 注册全局变量
var globalCache = NewCacheService()
builder.Services.AddInstance(globalCache)

注意:AddInstance 适用于应用启动时就需要创建的对象,或者外部传入的对象。

AddNamed - 注册命名服务

为同一类型注册多个实例,使用名称区分。

// 注册多个数据库连接
builder.Services.AddNamed("primary", func() *Database {
    return NewDatabase("primary-connection-string")
})

builder.Services.AddNamed("secondary", func() *Database {
    return NewDatabase("secondary-connection-string")
})

builder.Services.AddNamed("cache", func() *Database {
    return NewDatabase("cache-connection-string")
})
TryAdd - 条件注册

只在服务不存在时才注册,避免覆盖已有注册。

// 默认实现
builder.Services.TryAdd(NewDefaultEmailService)

// 用户可以在之前注册自定义实现来覆盖默认值
// 如果已注册,TryAdd 不会覆盖
builder.Services.Add(NewCustomEmailService)
builder.Services.TryAdd(NewDefaultEmailService)  // 不会注册

使用场景

  • 框架提供默认实现
  • 库提供可选的默认服务
  • 避免重复注册
AddHostedService - 注册后台服务

注册实现 IHostedService 接口的后台服务。

type BackgroundWorker struct {
    *hosting.BackgroundService
}

func NewBackgroundWorker() *BackgroundWorker {
    worker := &BackgroundWorker{
        BackgroundService: hosting.NewBackgroundService(),
    }
    worker.SetExecuteFunc(worker.doWork)
    return worker
}

func (w *BackgroundWorker) doWork(ctx context.Context) error {
    // 后台任务逻辑
    return nil
}

// 注册
builder.Services.AddHostedService(NewBackgroundWorker)

服务解析

Get - 泛型获取(推荐)

使用泛型 API 获取服务,类型安全且简洁。

// 获取指针类型服务
userService := di.Get[*UserService](provider)

// 获取值类型服务(自动解引用)
config := di.Get[AppConfig](provider)

// 如果服务不存在会 panic
GetOr - 带默认值获取

获取服务,如果不存在则返回默认值,不会 panic。

// 提供默认值
defaultConfig := &AppConfig{Port: 8080}
config := di.GetOr[*AppConfig](provider, defaultConfig)

// 适用于可选服务
logger := di.GetOr[*Logger](provider, nil)
if logger != nil {
    logger.Log("Service available")
}
TryGet - 安全获取

尝试获取服务,返回 (service, ok) 形式。

// 检查服务是否存在
if svc, ok := di.TryGet[*UserService](provider); ok {
    // 服务存在,使用它
    svc.DoSomething()
} else {
    // 服务不存在,使用替代逻辑
    log.Println("UserService not available")
}
GetNamed - 获取命名服务

根据名称获取特定的服务实例。

// 获取命名服务
primaryDB := di.GetNamed[*Database](provider, "primary")
secondaryDB := di.GetNamed[*Database](provider, "secondary")
cacheDB := di.GetNamed[*Database](provider, "cache")
GetAll - 获取所有实例

获取某个类型的所有已注册实例。

// 获取所有插件
plugins := di.GetAll[IPlugin](provider)
for _, plugin := range plugins {
    plugin.Initialize()
}

// 获取所有通知器
notifiers := di.GetAll[*INotifier](provider)
for _, notifier := range notifiers {
    notifier.Notify("Event occurred")
}
指针填充方式(可选)

除了泛型 API,也可以使用传统的指针填充方式:

// 获取服务
var userService *UserService
provider.Get(&userService)

// 获取命名服务
var primaryDB *Database
provider.GetNamed(&primaryDB, "primary")

依赖自动解析

构造函数注入

DI 容器会自动分析构造函数参数,并注入所需的依赖。

// 定义服务层级
type Repository struct{}

type Service struct {
    repo *Repository
}

type Controller struct {
    service *Service
    config  *Config
}

// 注册(按依赖顺序或任意顺序都可以)
builder.Services.Add(func() *Repository {
    return &Repository{}
})

builder.Services.Add(func(repo *Repository) *Service {
    return &Service{repo: repo}  // 自动注入 Repository
})

builder.Services.Add(func(svc *Service, cfg *Config) *Controller {
    return &Controller{
        service: svc,   // 自动注入 Service
        config:  cfg,   // 自动注入 Config
    }
})
依赖图

DI 容器会自动构建依赖图并按正确顺序创建实例:

Repository (无依赖)
    ↓
Service (依赖 Repository)
    ↓
Controller (依赖 Service, Config)
循环依赖检测

框架会自动检测循环依赖并报错:

// ❌ 循环依赖会导致 panic
type ServiceA struct {
    b *ServiceB
}

type ServiceB struct {
    a *ServiceA
}

builder.Services.Add(func(b *ServiceB) *ServiceA {
    return &ServiceA{b: b}
})

builder.Services.Add(func(a *ServiceA) *ServiceB {
    return &ServiceB{a: a}
})

// Build 时会检测到循环依赖并报错
app := builder.Build()  // panic: circular dependency detected

解决方案

  1. 重新设计服务架构,消除循环依赖
  2. 使用事件/消息模式解耦
  3. 延迟解析依赖(在需要时从 provider 获取)
可选依赖

使用 GetOrTryGet 实现可选依赖:

type Service struct {
    required *RequiredService
    optional *OptionalService
}

func NewService(
    provider di.IServiceProvider,
    required *RequiredService,
) *Service {
    // 可选依赖,如果不存在使用 nil
    optional := di.GetOr[*OptionalService](provider, nil)
    
    return &Service{
        required: required,
        optional: optional,
    }
}

资源管理

IDisposable 接口

实现 IDisposable 接口的服务会在应用关闭时自动释放资源。

type DatabaseConnection struct {
    db *sql.DB
}

func (d *DatabaseConnection) Dispose() error {
    if d.db != nil {
        return d.db.Close()
    }
    return nil
}

// 注册
builder.Services.Add(NewDatabaseConnection)

// 应用关闭时自动调用 Dispose
app := builder.Build()
defer app.Dispose()  // 自动释放所有 IDisposable 服务
释放顺序

服务按注册顺序的逆序释放(LIFO),确保依赖关系正确:

注册顺序:Repository → Service → Controller
释放顺序:Controller → Service → Repository

最佳实践

1. 优先使用构造函数注入
// ✅ 推荐:依赖通过构造函数注入
type UserService struct {
    repo   *UserRepository
    logger logging.ILogger
}

func NewUserService(repo *UserRepository, logger logging.ILogger) *UserService {
    return &UserService{
        repo:   repo,
        logger: logger,
    }
}

// ❌ 不推荐:在方法中手动获取依赖
type UserService struct {
    provider di.IServiceProvider
}

func (s *UserService) GetUser(id int) {
    repo := di.Get[*UserRepository](s.provider)  // 不推荐
}
2. 使用接口定义依赖
// 定义接口
type IUserRepository interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

// 实现接口
type UserRepository struct{}

func (r *UserRepository) FindByID(id int) (*User, error) {
    return nil, nil
}

func (r *UserRepository) Save(user *User) error {
    return nil
}

// 依赖接口而非具体实现
type UserService struct {
    repo IUserRepository  // 使用接口
}
3. 服务注册集中管理

创建模块化的服务注册函数:

// users/services.go
package users

func AddUserServices(services di.IServiceCollection) {
    services.Add(NewUserRepository)
    services.Add(NewUserService)
    services.Add(NewUserValidator)
}

// main.go
package main

func main() {
    builder := web.CreateBuilder()
    
    // 模块化注册
    users.AddUserServices(builder.Services)
    orders.AddOrderServices(builder.Services)
    auth.AddAuthServices(builder.Services)
    
    app := builder.Build()
    app.Run()
}
4. 使用泛型 API
// ✅ 推荐:类型安全的泛型 API
userService := di.Get[*UserService](provider)

// ⚠️ 可选:指针填充方式
var userService *UserService
provider.Get(&userService)
5. 合理使用命名服务
// ✅ 好的使用场景:多个相同类型的不同实例
services.AddNamed("mysql", NewMySQLDatabase)
services.AddNamed("postgres", NewPostgresDatabase)
services.AddNamed("redis", NewRedisDatabase)

// ❌ 不好的使用:应该使用不同的类型
services.AddNamed("user", NewUserService)
services.AddNamed("order", NewOrderService)
// 应该定义不同的服务类型
6. 避免服务定位器反模式
// ❌ 服务定位器反模式(不推荐)
type Service struct {
    provider di.IServiceProvider  // 持有 provider
}

func (s *Service) DoWork() {
    // 在方法中解析依赖
    repo := di.Get[*Repository](s.provider)
    logger := di.Get[*Logger](s.provider)
}

// ✅ 依赖注入(推荐)
type Service struct {
    repo   *Repository
    logger *Logger
}

func NewService(repo *Repository, logger *Logger) *Service {
    return &Service{
        repo:   repo,
        logger: logger,
    }
}
7. 单例服务注意线程安全
// ✅ 线程安全的单例服务
type CacheService struct {
    data map[string]interface{}
    mu   sync.RWMutex
}

func (c *CacheService) Get(key string) interface{} {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *CacheService) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

API 参考

泛型 API
// 获取服务(不存在会 panic)
func Get[T any](provider IServiceProvider) T

// 获取服务(不存在返回默认值)
func GetOr[T any](provider IServiceProvider, defaultValue T) T

// 尝试获取服务
func TryGet[T any](provider IServiceProvider) (T, bool)

// 获取命名服务
func GetNamed[T any](provider IServiceProvider, name string) T

// 获取所有服务
func GetAll[T any](provider IServiceProvider) []T
服务注册
// 注册单例服务
Add(constructor interface{}) IServiceCollection

// 注册实例
AddInstance(instance interface{}) IServiceCollection

// 注册命名服务
AddNamed(name string, constructor interface{}) IServiceCollection

// 条件注册
TryAdd(constructor interface{}) IServiceCollection

// 注册后台服务
AddHostedService(constructor interface{}) IServiceCollection
服务解析
// 获取服务(指针填充)
Get(target interface{})

// 获取命名服务(指针填充)
GetNamed(target interface{}, serviceKey string)

// 释放资源
Dispose() error

常见问题

如何注册接口?

Go 的反射机制不能直接注册接口类型,需要注册具体实现:

// 定义接口
type IUserService interface {
    GetUser(id int) (*User, error)
}

// 实现接口
type UserService struct {}

func (s *UserService) GetUser(id int) (*User, error) {
    return nil, nil
}

// ✅ 注册具体实现
services.Add(func() *UserService {
    return &UserService{}
})

// 使用时转换为接口
userService := di.Get[*UserService](provider)
var svc IUserService = userService  // 赋值给接口变量
如何处理初始化失败?

构造函数可以返回 error:

services.Add(func() (*Database, error) {
    db, err := sql.Open("postgres", "...")
    if err != nil {
        return nil, fmt.Errorf("failed to open database: %w", err)
    }
    return &Database{db: db}, nil
})

// Build 时如果初始化失败会 panic
app := builder.Build()  // 可能 panic
服务什么时候创建?

Singleton 服务在首次请求时创建(懒加载),之后复用同一实例。

services.Add(NewUserService)  // 注册,不创建
provider := services.Build()   // 构建,不创建
svc := di.Get[*UserService](provider)  // 首次获取时创建
svc2 := di.Get[*UserService](provider) // 复用已创建的实例
可以运行时动态注册服务吗?

不可以。所有服务必须在调用 Build() 之前注册完成。Build 后服务注册就被冻结了。

services.Add(NewService1)
provider := services.Build()  // Build 后不能再注册

services.Add(NewService2)  // ❌ 无效,Build 后的注册被忽略

← 返回主目录

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Get

func Get[T any](provider IServiceProvider) T

Get retrieves a service from the provider (panics if not found). Supports both pointer and value types with auto-dereferencing:

  • userSvc := di.Get[*UserService](provider) // pointer type (zero-copy)
  • config := di.Get[AppConfig](provider) // value type (auto-deref + copy)

func GetAll

func GetAll[T any](provider IServiceProvider) []T

GetAll retrieves all services of the specified type. Returns a slice of all registered services matching the type.

  • plugins := di.GetAll[IPlugin](provider)

func GetNamed

func GetNamed[T any](provider IServiceProvider, name string) T

GetNamed retrieves a named service from the provider (panics if not found). Useful for services registered with a specific key. Supports auto-dereferencing like Get:

  • primaryDB := di.GetNamed[*Database](provider, "primary") // pointer
  • config := di.GetNamed[AppConfig](provider, "app") // value (auto-deref)

func GetOr

func GetOr[T any](provider IServiceProvider, defaultValue T) T

GetOr retrieves a service from the provider, returns defaultValue if not found. Does not panic, useful for optional services. Supports auto-dereferencing.

  • config := di.GetOr[*Config](provider, defaultConfig)
  • val := di.GetOr[AppConfig](provider, defaultVal)

func TryGet

func TryGet[T any](provider IServiceProvider) (T, bool)

TryGet attempts to retrieve a service from the provider. Returns the service and true if found, zero value and false otherwise. Supports auto-dereferencing like Get.

  • if svc, ok := di.TryGet[*Service](provider); ok { ... }
  • if val, ok := di.TryGet[AppConfig](provider); ok { ... }

Types

type IAsyncDisposable

type IAsyncDisposable interface {
	// DisposeAsync asynchronously releases all resources held by the object.
	// The context can be used to control cancellation and timeouts.
	// It should be safe to call DisposeAsync multiple times.
	// Returns an error if resource cleanup fails.
	DisposeAsync(ctx context.Context) error
}

IAsyncDisposable defines an interface for resources that require asynchronous cleanup. This is useful for services that need to perform I/O operations during disposal, such as flushing buffers or gracefully closing network connections.

Corresponds to .NET's IAsyncDisposable interface.

type IDisposable

type IDisposable interface {
	// Dispose releases all resources held by the object.
	// It should be safe to call Dispose multiple times.
	// Returns an error if resource cleanup fails.
	Dispose() error
}

IDisposable defines an interface for resources that require explicit cleanup. Services implementing this interface will have their Dispose method called automatically when the owning scope or service provider is disposed.

Corresponds to .NET's IDisposable interface.

type IServiceCollection

type IServiceCollection interface {
	// Add 使用构造函数注册单例服务。
	Add(constructor any) IServiceCollection

	// AddInstance 注册单例实例(预先创建的对象)。
	AddInstance(instance any) IServiceCollection

	// AddNamed 注册命名单例服务。
	AddNamed(name string, constructor any) IServiceCollection

	// TryAdd 尝试添加单例服务(如果不存在)。
	TryAdd(constructor any) IServiceCollection

	// AddHostedService 注册托管服务(后台服务)。
	AddHostedService(constructor any) IServiceCollection
}

IServiceCollection 是服务描述符集合的契约。 这是一个遵循接口隔离原则的纯注册接口。 Build、Count 和 GetDescriptors 方法在具体类型上可用,但不在接口中。

func NewServiceCollection

func NewServiceCollection() IServiceCollection

NewServiceCollection 创建一个新的服务集合。

type IServiceProvider

type IServiceProvider interface {
	// Get 检索服务并将其填充到目标指针中(如果未找到则 panic)。
	// 支持自动解引用的指针和值类型:
	//   - var svc *UserService; provider.Get(&svc)  // 指针类型(零拷贝)
	//   - var svc UserService; provider.Get(&svc)   // 值类型(自动解引用 + 拷贝)
	Get(target interface{})

	// GetNamed 检索命名服务并将其填充到目标指针中(如果未找到则 panic)。
	//   - var db *Database; provider.GetNamed(&db, "primary")
	GetNamed(target interface{}, serviceKey string)

	// Dispose 释放所有资源。
	Dispose() error
	// contains filtered or unexported methods
}

IServiceProvider 定义检索服务对象的机制。

func BuildServiceProvider

func BuildServiceProvider(services IServiceCollection) IServiceProvider

BuildServiceProvider 从服务集合构建服务提供者。 该函数允许从接口类型构建。 用法:provider := di.BuildServiceProvider(services)

type ServiceDescriptor

type ServiceDescriptor struct {
	// ServiceType is the type of the service (usually an interface).
	ServiceType reflect.Type

	// ImplementationType is the concrete type that implements the service.
	ImplementationType reflect.Type

	// Lifetime specifies when to create service instances.
	Lifetime ServiceLifetime

	// ServiceKey is used for keyed services (optional).
	ServiceKey string

	// Factory is the function that creates the service instance.
	Factory interface{}

	// Instance is a pre-created singleton instance (optional).
	Instance interface{}

	// Interfaces lists additional interface types this service implements.
	Interfaces []reflect.Type
}

ServiceDescriptor describes a service registration.

type ServiceLifetime

type ServiceLifetime int

ServiceLifetime specifies the lifetime of a service in the dependency injection container. In this simplified DI container, only Singleton lifetime is supported.

const (
	// Singleton specifies that a single instance of the service will be created
	// and shared across the entire application lifetime.
	Singleton ServiceLifetime = iota
)

func (ServiceLifetime) String

func (l ServiceLifetime) String() string

String returns the string representation of the ServiceLifetime.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL