Slim

ioc - Inversion of Control Container

ioc is a lightweight inversion of control container for Go applications, with dependency binding, factories, automatic injection, and layered containers.

ioc 是一个功能强大且轻量级的控制反转 (IoC) 容器,为 Go 应用程序提供全面的依赖注入功能。支持实例绑定、工厂函数、自动依赖注入、分层容器等特性,适用于构建可维护的大型应用。最低支持 Go 1.21.0 版本。

安装

go get -u go-slim.dev/ioc

快速开始

package mainimport (    "context"    "fmt"    "log"    "go-slim.dev/ioc")type Database struct {    Host string}type UserService struct {    DB *Database `ioc:""` // 使用标签注入依赖}func main() {    // 绑定依赖    ioc.Bind(&Database{Host: "localhost"})    // 创建服务并自动注入依赖    svc := &UserService{}    if err := ioc.Resolve(svc); err != nil {        log.Fatal(err)    }    fmt.Println(svc.DB.Host) // localhost}

核心概念

容器 (Container)

容器是 IoC 的核心组件,负责管理服务的注册、创建和生命周期。容器提供两种使用方式:

  1. 默认容器:通过包级函数使用全局容器
  2. 自定义容器:创建独立的容器实例
// 使用默认容器ioc.Bind(&MyService{})// 创建自定义容器container := ioc.New()container.Bind(&MyService{})

绑定类型

IoC 容器支持三种绑定方式:

  1. 实例绑定:绑定已创建的实例
  2. 命名绑定:使用名称区分同一类型的多个实现
  3. 工厂绑定:注册工厂函数延迟创建实例

解析顺序

当请求依赖时,容器按以下顺序查找:

  1. 实例缓存中的精确类型匹配
  2. 工厂函数中的精确类型匹配
  3. 类型兼容的实例(实现接口或可赋值)
  4. 类型兼容的工厂(实现接口或可赋值)
  5. 父容器(如果存在)
  6. 自动创建(仅限结构体和结构体指针)

基本用法

1. 实例绑定

Bind - 绑定实例

func Bind(instance any)

说明

将预创建的实例绑定到默认容器。每个类型只能有一个未命名的绑定。

示例

type Database struct {    Host string    Port int}// 绑定数据库实例ioc.Bind(&Database{    Host: "localhost",    Port: 5432,})// 获取实例ctx := context.Background()db, err := ioc.Get[*Database](ctx)if err != nil {    log.Fatal(err)}fmt.Println((*db).Host) // localhost

使用场景

  • 绑定配置对象
  • 绑定单例服务
  • 绑定外部创建的资源

NamedBind - 命名绑定

func NamedBind(name string, instance any)

说明

使用名称绑定实例,允许同一类型有多个不同的实现。命名绑定不会覆盖未命名的绑定。

示例

type Logger struct {    Level string}// 绑定多个日志记录器ioc.NamedBind("debug", &Logger{Level: "DEBUG"})ioc.NamedBind("info", &Logger{Level: "INFO"})ioc.NamedBind("error", &Logger{Level: "ERROR"})// 按名称获取ctx := context.Background()debugLogger, _ := ioc.NamedGet[*Logger](ctx, "debug")infoLogger, _ := ioc.NamedGet[*Logger](ctx, "info")fmt.Println((*debugLogger).Level) // DEBUGfmt.Println((*infoLogger).Level)  // INFO

使用场景

  • 同一接口的多个实现
  • 不同环境的配置
  • 多租户场景

2. 工厂函数

Factory - 注册工厂函数

func Factory(factory any, shared ...bool) error

说明

注册工厂函数以实现延迟创建。工厂函数在首次请求时才会被调用。

参数

  • factory:工厂函数,可以接受依赖参数
  • shared:可选参数,true 表示单例(结果被缓存),false 表示每次创建新实例

工厂函数签名

  • 无参数:func() Tfunc() (T, error)
  • 有参数:func(dep1 T1, dep2 T2) Tfunc(dep1 T1) (T, error)

示例

type Config struct {    Port int}type Server struct {    Port int}// 简单工厂ioc.Factory(func() *Config {    return &Config{Port: 8080}})// 带依赖的工厂ioc.Factory(func(cfg *Config) *Server {    return &Server{Port: cfg.Port}}, true) // shared = true,结果被缓存// 带错误处理的工厂ioc.Factory(func(cfg *Config) (*Database, error) {    if cfg.Port == 0 {        return nil, errors.New("port is required")    }    return &Database{Port: cfg.Port}, nil}, true)// 获取实例(工厂会自动执行)ctx := context.Background()server, _ := ioc.Get[*Server](ctx)

注意事项

  • 工厂函数的参数会自动从容器注入
  • 工厂函数不能依赖其返回的类型(防止循环依赖)
  • shared=true 时结果会被缓存,后续请求返回同一实例
  • shared=false 或未指定时,每次请求都会调用工厂创建新实例

使用场景

  • 需要延迟初始化的服务
  • 依赖其他服务的复杂对象
  • 需要控制生命周期的资源

NamedFactory - 命名工厂

func NamedFactory(name string, factory any, shared ...bool) error

说明

注册命名的工厂函数,与 NamedBind 类似,允许同一类型有多个工厂。

示例

type Cache interface {    Get(key string) string    Set(key, value string)}type RedisCache struct { Name string }type MemCache struct { Name string }// 注册多个缓存实现ioc.NamedFactory("redis", func() Cache {    return &RedisCache{Name: "Redis"}}, true)ioc.NamedFactory("memcached", func() Cache {    return &MemCache{Name: "Memcached"}}, true)// 按名称获取ctx := context.Background()redis, _ := ioc.NamedGet[Cache](ctx, "redis")memcached, _ := ioc.NamedGet[Cache](ctx, "memcached")

3. 依赖注入

Resolve - 解析依赖

func Resolve(i any) error

说明

对结构体执行依赖注入。通过 ioc 标签指定要注入的依赖。

标签格式

  • ioc:"":注入未命名的依赖
  • ioc:"name":注入指定名称的依赖
  • ioc:",optional":可选依赖,找不到时不报错
  • ioc:"name,optional":命名的可选依赖

示例

type Logger struct {    Level string}type Database struct {    Host string}type UserService struct {    Logger   *Logger   `ioc:""`           // 注入未命名的 Logger    DB       *Database `ioc:""`           // 注入未命名的 Database    Cache    *Cache    `ioc:",optional"`  // 可选依赖}// 绑定依赖ioc.Bind(&Logger{Level: "INFO"})ioc.Bind(&Database{Host: "localhost"})// 创建并解析svc := &UserService{}if err := ioc.Resolve(svc); err != nil {    log.Fatal(err)}fmt.Println(svc.Logger.Level) // INFOfmt.Println(svc.DB.Host)      // localhostfmt.Println(svc.Cache)        // nil (可选依赖未绑定)

命名依赖示例

type Service struct {    PrimaryDB   *Database `ioc:"primary"`    SecondaryDB *Database `ioc:"secondary"`    Logger      *Logger   `ioc:"app"`}// 绑定命名依赖ioc.NamedBind("primary", &Database{Host: "db1.example.com"})ioc.NamedBind("secondary", &Database{Host: "db2.example.com"})ioc.NamedBind("app", &Logger{Level: "INFO"})// 解析svc := &Service{}ioc.Resolve(svc)

注意事项

  • 必须传入结构体指针
  • 字段必须是导出的(首字母大写)
  • 嵌套结构体会递归注入
  • 可选依赖找不到时不会报错,字段保持零值

自动创建

说明

当请求的类型是结构体或结构体指针,且未显式绑定时,容器会自动创建实例并注入其依赖。

示例

type Logger struct {    Level string}type Database struct {    Logger *Logger `ioc:""` // Logger 未绑定,会自动创建}type Service struct {    DB *Database `ioc:""` // Database 未绑定,会自动创建}// 不绑定任何东西,直接解析svc := &Service{}ioc.Resolve(svc)// Service.DB 被自动创建// Service.DB.Logger 也被自动创建fmt.Println(svc.DB != nil)        // truefmt.Println(svc.DB.Logger != nil) // true

自动创建与可选依赖

type Service struct {    Cache *Cache `ioc:",optional"` // 可选依赖}svc := &Service{}ioc.Resolve(svc)// 如果 Cache 未显式绑定,即使可以自动创建也会跳过fmt.Println(svc.Cache) // nil

注意事项

  • 只有结构体和结构体指针类型支持自动创建
  • 接口类型不会自动创建(必须显式绑定或使用工厂)
  • 可选字段不会自动创建
  • 自动创建的实例会递归注入其依赖

4. 获取实例

Get - 获取实例

func Get[T any](ctx context.Context) (*T, error)

说明

从容器获取指定类型的实例。优先从上下文中的容器获取,否则使用默认容器。

示例

type Config struct {    Port int}ioc.Bind(&Config{Port: 8080})ctx := context.Background()config, err := ioc.Get[*Config](ctx)if err != nil {    log.Fatal(err)}fmt.Println((*config).Port) // 8080

类型参数说明

// 结构体指针config, _ := ioc.Get[*Config](ctx)      // 推荐value := *config// 接口类型logger, _ := ioc.Get[Logger](ctx)       // Logger 是接口logger.Log("message")

NamedGet - 获取命名实例

func NamedGet[T any](ctx context.Context, name string) (*T, error)

说明

按名称获取指定类型的实例。

示例

type Database struct {    Name string}ioc.NamedBind("primary", &Database{Name: "Primary"})ioc.NamedBind("secondary", &Database{Name: "Secondary"})ctx := context.Background()primary, _ := ioc.NamedGet[*Database](ctx, "primary")secondary, _ := ioc.NamedGet[*Database](ctx, "secondary")fmt.Println((*primary).Name)   // Primaryfmt.Println((*secondary).Name) // Secondary

GetFrom - 从指定容器获取

func GetFrom[T any](c *Container) (*T, error)

说明

从指定的容器获取实例,不使用上下文。

示例

// 创建自定义容器container := ioc.New()container.Bind(&Config{Port: 9090})// 从指定容器获取config, _ := ioc.GetFrom[*Config](container)fmt.Println((*config).Port) // 9090// 默认容器中的实例不受影响defaultConfig, _ := ioc.Get[*Config](context.Background())// 如果默认容器中没有绑定,会返回错误

NamedGetFrom - 从指定容器获取命名实例

func NamedGetFrom[T any](c *Container, name string) (*T, error)

说明

从指定容器按名称获取实例。

示例

container := ioc.New()container.NamedBind("redis", &Cache{Type: "Redis"})cache, _ := ioc.NamedGetFrom[*Cache](container, "redis")

5. 函数调用

Invoke - 调用函数

func Invoke(ctx context.Context, fn any) ([]reflect.Value, error)

说明

执行函数并自动注入其参数。函数的所有参数都会从容器解析并注入。

示例

type Database struct {    Host string}type Logger struct {    Level string}ioc.Bind(&Database{Host: "localhost"})ioc.Bind(&Logger{Level: "INFO"})// 函数参数自动注入results, err := ioc.Invoke(context.Background(), func(db *Database, log *Logger) string {    return fmt.Sprintf("Connected to %s, logging at %s", db.Host, log.Level)})if err != nil {    log.Fatal(err)}message := results[0].Interface().(string)fmt.Println(message) // Connected to localhost, logging at INFO

多返回值示例

results, _ := ioc.Invoke(ctx, func(db *Database) (string, int, error) {    return db.Host, db.Port, nil})host := results[0].Interface().(string)port := results[1].Interface().(int)err := results[2].Interface().(error)

Call - 单返回值调用

func Call[T any](ctx context.Context, fn any) (T, error)

说明

执行只有一个返回值的函数,提供类型安全的结果。

示例

type Greeter struct {    Prefix string}ioc.Bind(&Greeter{Prefix: "Hello"})// 调用函数并获取类型化结果greeting, err := ioc.Call[string](context.Background(), func(g *Greeter) string {    return g.Prefix + ", World!"})if err != nil {    log.Fatal(err)}fmt.Println(greeting) // Hello, World!

错误处理

// 函数返回多个值会报错_, err := ioc.Call[string](ctx, func() (string, int) {    return "hello", 42})// 错误:function returned multiple values, use Call2 or Invoke instead// 返回类型不匹配会报错_, err := ioc.Call[int](ctx, func() string {    return "hello"})// 错误:function return type string does not match expected type int

Call2 - 双返回值调用

func Call2[T any](ctx context.Context, fn any) (T, error)

说明

执行返回 (T, error) 的函数,遵循 Go 的常见错误处理模式。

示例

type UserRepository struct {    DB *Database}func (r *UserRepository) FindByID(id int) (*User, error) {    // 查询逻辑...    return user, nil}ioc.Factory(func(db *Database) *UserRepository {    return &UserRepository{DB: db}}, true)// 调用返回 (T, error) 的函数user, err := ioc.Call2[*User](ctx, func(repo *UserRepository) (*User, error) {    return repo.FindByID(123)})if err != nil {    log.Fatal(err)}fmt.Println(user.Name)

函数内部错误会被传递

result, err := ioc.Call2[string](ctx, func() (string, error) {    return "", errors.New("something went wrong")})// err 是函数返回的错误fmt.Println(err) // something went wrong

高级特性

1. 分层容器

NewChild - 创建子容器

func NewChild() *Container

说明

创建默认容器的子容器。子容器继承父容器的服务,但可以覆盖它们而不影响父容器。

特性

  • 子容器初始为空(不复制父容器的绑定)
  • 查找时会向上查找父容器
  • 子容器的绑定不会影响父容器
  • 适用于创建请求级或测试级作用域

示例

type Config struct {    Env string}// 在默认容器中绑定生产配置ioc.Bind(&Config{Env: "production"})// 创建子容器用于测试testContainer := ioc.NewChild()testContainer.Bind(&Config{Env: "testing"})ctx := context.Background()// 默认容器返回生产配置prodConfig, _ := ioc.Get[*Config](ctx)fmt.Println((*prodConfig).Env) // production// 子容器返回测试配置testConfig, _ := ioc.GetFrom[*Config](testContainer)fmt.Println((*testConfig).Env) // testing// 默认容器不受影响prodConfig2, _ := ioc.Get[*Config](ctx)fmt.Println((*prodConfig2).Env) // production

继承查找示例

type Logger struct {    Level string}type Cache struct {    Type string}// 父容器绑定ioc.Bind(&Logger{Level: "INFO"})// 子容器child := ioc.NewChild()child.Bind(&Cache{Type: "Redis"})// 子容器可以访问父容器的服务logger, _ := ioc.GetFrom[*Logger](child)fmt.Println((*logger).Level) // INFO (来自父容器)cache, _ := ioc.GetFrom[*Cache](child)fmt.Println((*cache).Type) // Redis (来自子容器)// 父容器看不到子容器的服务_, err := ioc.Get[*Cache](context.Background())fmt.Println(err) // ioc: value not found

使用场景

  • 为每个 HTTP 请求创建独立容器
  • 测试时覆盖特定依赖
  • 多租户应用的租户级容器

2. 上下文集成

NewContext - 创建带容器的上下文

func NewContext(parentCtx context.Context) context.Context

说明

创建包含默认容器的上下文。通过上下文传递容器,实现请求级依赖管理。

示例

type RequestID struct {    ID string}func handleRequest(w http.ResponseWriter, r *http.Request) {    // 创建请求级容器    reqContainer := ioc.New()    reqContainer.Bind(&RequestID{ID: generateID()})    // 将容器放入上下文    ctx := reqContainer.NewContext(r.Context())    // 传递上下文给处理函数    processRequest(ctx)}func processRequest(ctx context.Context) {    // 从上下文获取请求 ID    reqID, _ := ioc.Get[*RequestID](ctx)    fmt.Println("Processing request:", (*reqID).ID)}

FromContext - 从上下文获取容器

func FromContext(ctx context.Context) (*Container, bool)

说明

从上下文中获取容器实例。

示例

func middleware(ctx context.Context) {    if container, ok := ioc.FromContext(ctx); ok {        // 使用上下文中的容器        config, _ := ioc.GetFrom[*Config](container)        fmt.Println((*config).Port)    } else {        // 上下文中没有容器,使用默认容器        config, _ := ioc.Get[*Config](ctx)    }}

3. 容器方法

自定义容器支持与包级函数相同的方法:

container := ioc.New()// 绑定方法container.Bind(&MyService{})container.NamedBind("primary", &Database{})container.Factory(func() *Service { return &Service{} }, true)container.NamedFactory("redis", func() Cache { return &RedisCache{} }, true)// 获取方法value, err := container.Get(reflect.TypeOf((*MyService)(nil)))value, err := container.NamedGet("primary", reflect.TypeOf((*Database)(nil)))// 解析方法container.Resolve(&myStruct)// 调用方法results, err := container.Invoke(func(svc *MyService) string {    return svc.DoSomething()})// 创建子容器child := container.NewChild()

错误处理

错误类型

var (    ErrValueNotFound        // 容器中找不到请求的值    ErrFactoryFailed        // 工厂函数执行失败    ErrInvalidType          // 无效的类型    ErrDependencyResolution // 依赖解析失败)

错误处理示例

import "errors"type Config struct {    Value string}config, err := ioc.Get[*Config](ctx)if err != nil {    if errors.Is(err, ioc.ErrValueNotFound) {        // 处理未找到的情况        log.Println("Config not registered, using defaults")        config = &(&Config{Value: "default"})    } else if errors.Is(err, ioc.ErrFactoryFailed) {        // 处理工厂失败        log.Printf("Factory failed: %v", err)        return err    } else {        // 其他错误        log.Fatal(err)    }}

工厂错误处理

ioc.Factory(func() (*Database, error) {    db, err := sql.Open("postgres", dsn)    if err != nil {        return nil, fmt.Errorf("failed to connect: %w", err)    }    return &Database{conn: db}, nil}, true)// 获取时会传递工厂的错误db, err := ioc.Get[*Database](ctx)if err != nil {    // 这里的 err 包含了工厂函数返回的错误    log.Printf("Database initialization failed: %v", err)}

使用场景

1. Web 应用依赖注入

package mainimport (    "context"    "go-slim.dev/ioc"    "go-slim.dev/slim")// 定义服务type Database struct {    DSN string}type UserRepository struct {    DB *Database}type UserService struct {    Repo *UserRepository}func main() {    // 注册依赖    ioc.Bind(&Database{DSN: "postgres://..."})    ioc.Factory(func(db *Database) *UserRepository {        return &UserRepository{DB: db}    }, true)    ioc.Factory(func(repo *UserRepository) *UserService {        return &UserService{Repo: repo}    }, true)    // 创建 Web 服务器    app := slim.New()    // 中间件:将容器放入上下文    app.Use(func(next slim.HandlerFunc) slim.HandlerFunc {        return func(c slim.Context) error {            ctx := ioc.NewContext(c.Request().Context())            c.SetRequest(c.Request().WithContext(ctx))            return next(c)        }    })    // 路由处理器    app.GET("/users/:id", func(c slim.Context) error {        ctx := c.Request().Context()        // 使用 Call2 调用业务逻辑        user, err := ioc.Call2[*User](ctx, func(svc *UserService) (*User, error) {            id := c.PathParam("id")            return svc.Repo.FindByID(id)        })        if err != nil {            return c.JSON(500, map[string]string{"error": err.Error()})        }        return c.JSON(200, user)    })    app.Start(":8080")}

2. 多环境配置管理

package mainimport (    "os"    "go-slim.dev/ioc")type Config struct {    Env      string    DBHost   string    LogLevel string}func main() {    env := os.Getenv("APP_ENV")    // 根据环境注册不同配置    switch env {    case "production":        ioc.Bind(&Config{            Env:      "production",            DBHost:   "prod-db.example.com",            LogLevel: "error",        })    case "staging":        ioc.Bind(&Config{            Env:      "staging",            DBHost:   "staging-db.example.com",            LogLevel: "warn",        })    default:        ioc.Bind(&Config{            Env:      "development",            DBHost:   "localhost",            LogLevel: "debug",        })    }    // 应用代码使用统一的接口获取配置    ctx := context.Background()    cfg, _ := ioc.Get[*Config](ctx)    fmt.Printf("Running in %s environment\n", cfg.Env)}

3. 测试中使用 IoC

package serviceimport (    "testing"    "go-slim.dev/ioc")// 真实的数据库实现type Database struct {    conn *sql.DB}func (db *Database) Query(sql string) ([]Row, error) {    // 真实查询}// 模拟的数据库实现type MockDatabase struct {    data map[string][]Row}func (db *MockDatabase) Query(sql string) ([]Row, error) {    return db.data[sql], nil}// 测试函数func TestUserService(t *testing.T) {    // 创建测试容器    testContainer := ioc.New()    // 注册模拟依赖    mockDB := &MockDatabase{        data: map[string][]Row{            "SELECT * FROM users": {{ID: 1, Name: "Alice"}},        },    }    testContainer.Bind(Database(mockDB))    // 注册服务    testContainer.Factory(func(db Database) *UserService {        return &UserService{DB: db}    }, true)    // 获取服务并测试    svc, _ := ioc.GetFrom[*UserService](testContainer)    users, err := svc.GetAllUsers()    if err != nil {        t.Fatal(err)    }    if len(users) != 1 || users[0].Name != "Alice" {        t.Error("Expected 1 user named Alice")    }}

4. 多租户应用

package mainimport (    "go-slim.dev/ioc")type Tenant struct {    ID   string    Name string}type TenantConfig struct {    DatabaseDSN string    CacheHost   string}func handleTenantRequest(tenantID string) {    // 为每个租户创建独立容器    tenantContainer := ioc.NewChild()    // 注册租户特定的配置    tenant := loadTenant(tenantID)    tenantContainer.Bind(tenant)    config := loadTenantConfig(tenantID)    tenantContainer.Bind(config)    // 注册租户特定的服务    tenantContainer.Factory(func(cfg *TenantConfig) *Database {        return connectDatabase(cfg.DatabaseDSN)    }, true)    // 处理请求    ctx := tenantContainer.NewContext(context.Background())    processRequest(ctx)}func processRequest(ctx context.Context) {    // 获取租户特定的依赖    tenant, _ := ioc.Get[*Tenant](ctx)    db, _ := ioc.Get[*Database](ctx)    fmt.Printf("Processing for tenant: %s\n", tenant.Name)}

5. 插件系统

package mainimport "go-slim.dev/ioc"// 插件接口type Plugin interface {    Name() string    Initialize() error}// 注册多个插件func registerPlugins() {    ioc.NamedBind("auth", &AuthPlugin{})    ioc.NamedBind("logging", &LoggingPlugin{})    ioc.NamedBind("metrics", &MetricsPlugin{})}// 初始化所有插件func initializePlugins() error {    plugins := []string{"auth", "logging", "metrics"}    for _, name := range plugins {        plugin, err := ioc.NamedGet[Plugin](context.Background(), name)        if err != nil {            return err        }        if err := (*plugin).Initialize(); err != nil {            return fmt.Errorf("failed to initialize plugin %s: %w", name, err)        }        fmt.Printf("Plugin %s initialized\n", (*plugin).Name())    }    return nil}

最佳实践

1. 在程序启动时注册依赖

func main() {    // ✓ 推荐:启动时注册所有依赖    registerDependencies()    // 然后启动应用    startApplication()}func registerDependencies() {    // 配置    ioc.Bind(&Config{})    // 基础服务    ioc.Factory(func(cfg *Config) *Database {        return connectDB(cfg.DSN)    }, true)    ioc.Factory(func() *Logger {        return newLogger()    }, true)    // 业务服务    ioc.Factory(func(db *Database, log *Logger) *UserService {        return &UserService{DB: db, Logger: log}    }, true)}

2. 使用工厂函数而非直接绑定

// ✗ 不推荐:直接绑定(无法声明依赖)db := connectDatabase()logger := newLogger()svc := &UserService{DB: db, Logger: logger}ioc.Bind(svc)// ✓ 推荐:使用工厂(依赖自动注入)ioc.Factory(func(db *Database, log *Logger) *UserService {    return &UserService{DB: db, Logger: log}}, true)

3. 为昂贵资源使用单例

// ✓ 数据库连接应该是单例ioc.Factory(func(cfg *Config) *Database {    return connectDB(cfg.DSN)}, true) // shared = true// ✓ 日志器应该是单例ioc.Factory(func() *Logger {    return newLogger()}, true)// ✗ 请求对象不应该是单例ioc.Factory(func() *Request {    return &Request{Timestamp: time.Now()}}, false) // shared = false

4. 使用接口而非具体类型

// 定义接口type Logger interface {    Log(msg string)}type Database interface {    Query(sql string) ([]Row, error)}// 绑定接口的实现ioc.Bind(Logger(&ConsoleLogger{}))ioc.Bind(Database(&PostgresDB{}))// 服务依赖接口type UserService struct {    Logger Logger   `ioc:""`    DB     Database `ioc:""`}

5. 使用命名绑定区分多个实现

// ✓ 推荐:使用命名绑定ioc.NamedBind("primary", &Database{Host: "db1"})ioc.NamedBind("secondary", &Database{Host: "db2"})type Service struct {    PrimaryDB   *Database `ioc:"primary"`    SecondaryDB *Database `ioc:"secondary"`}// ✗ 不推荐:创建包装类型type PrimaryDatabase Databasetype SecondaryDatabase Database

6. 为测试创建子容器

func TestService(t *testing.T) {    // ✓ 推荐:使用子容器隔离测试    testContainer := ioc.New()    testContainer.Bind(&MockDatabase{})    svc, _ := ioc.GetFrom[*UserService](testContainer)    // 测试代码...}// ✗ 不推荐:修改默认容器(会影响其他测试)func TestServiceBad(t *testing.T) {    ioc.Bind(&MockDatabase{}) // 污染默认容器    // 测试代码...}

7. 标记可选依赖

type Service struct {    Logger *Logger `ioc:""`           // 必需    Cache  *Cache  `ioc:",optional"`  // 可选    Trace  *Tracer `ioc:",optional"`  // 可选}// Logger 必须绑定,否则 Resolve 会失败// Cache 和 Trace 可以不绑定

8. 避免循环依赖

// ✗ 错误:循环依赖ioc.Factory(func(b *ServiceB) *ServiceA {    return &ServiceA{B: b}}, true)ioc.Factory(func(a *ServiceA) *ServiceB {    return &ServiceB{A: a}}, true)// 会导致栈溢出或死锁// ✓ 解决方案:引入中间层或使用接口type ServiceC struct {    Data string}ioc.Factory(func(c *ServiceC) *ServiceA {    return &ServiceA{C: c}}, true)ioc.Factory(func(c *ServiceC) *ServiceB {    return &ServiceB{C: c}}, true)

线程安全

IoC 容器是线程安全的,可以在多个 goroutine 中并发使用:

func main() {    // 注册依赖(单线程)    ioc.Factory(func() *Service {        return &Service{}    }, true)    // 并发使用(多线程安全)    var wg sync.WaitGroup    for i := 0; i < 100; i++ {        wg.Add(1)        go func() {            defer wg.Done()            svc, _ := ioc.Get[*Service](context.Background())            // 使用 svc...        }()    }    wg.Wait()}

内部使用了细粒度的读写锁,最小化锁竞争:

  • 实例缓存和工厂注册使用独立的 RWMutex
  • 读操作(Get、Resolve 等)使用读锁,允许并发
  • 写操作(Bind、Factory 等)使用写锁,独占访问

API 参考

包级函数

容器管理

函数说明
Default() *Container获取默认容器
NewChild() *Container创建子容器
NewContext(context.Context) context.Context创建带容器的上下文
FromContext(context.Context) (*Container, bool)从上下文获取容器

绑定函数

函数说明
Bind(instance any)绑定实例
NamedBind(name string, instance any)绑定命名实例
Factory(factory any, shared ...bool) error注册工厂函数
NamedFactory(name string, factory any, shared ...bool) error注册命名工厂函数

解析函数

函数说明
Get[T any](ctx context.Context) (*T, error)获取实例
NamedGet[T any](ctx context.Context, name string) (*T, error)获取命名实例
GetFrom[T any](c *Container) (*T, error)从指定容器获取实例
NamedGetFrom[T any](c *Container, name string) (*T, error)从指定容器获取命名实例
Resolve(i any) error执行依赖注入

调用函数

函数说明
Invoke(ctx context.Context, fn any) ([]reflect.Value, error)调用函数并注入参数
Call[T any](ctx context.Context, fn any) (T, error)调用单返回值函数
Call2[T any](ctx context.Context, fn any) (T, error)调用 (T, error) 返回值的函数

Container 类型

方法

方法说明
New() *Container创建新容器
NewChild() *Container创建子容器
Bind(value any)绑定实例
NamedBind(name string, value any)绑定命名实例
Factory(factory any, shared ...bool) error注册工厂函数
NamedFactory(name string, factory any, shared ...bool) error注册命名工厂函数
Get(t reflect.Type) (reflect.Value, error)获取实例
NamedGet(name string, t reflect.Type) (reflect.Value, error)获取命名实例
Resolve(i any) error执行依赖注入
Invoke(fn any) ([]reflect.Value, error)调用函数
NewContext(context.Context) context.Context创建带容器的上下文

注意事项

1. 泛型类型参数

使用 Get 等泛型函数时,类型参数应该是指针类型:

// ✓ 推荐config, _ := ioc.Get[*Config](ctx)value := *config// ✗ 不推荐(会导致类型断言问题)config, _ := ioc.Get[Config](ctx)

2. 工厂函数的返回值

工厂函数必须返回具体类型或接口:

// ✓ 正确:返回具体类型ioc.Factory(func() *Database {    return &Database{}}, true)// ✓ 正确:返回接口ioc.Factory(func() Logger {    return &ConsoleLogger{}}, true)// ✓ 正确:返回 (值, 错误)ioc.Factory(func() (*Database, error) {    return &Database{}, nil}, true)// ✗ 错误:返回多个非错误值ioc.Factory(func() (*Database, *Logger) {    return &Database{}, &Logger{}}, true)

3. 循环依赖检测

容器会检测工厂函数的直接循环依赖:

// ✗ 错误:工厂依赖自己的返回类型ioc.Factory(func(db *Database) *Database {    return db}, true)// 返回错误:ioc: factory function signature is invalid - depends on abstract it returns

但不检测间接循环依赖,需要开发者注意。

4. 未导出字段不能注入

type Service struct {    logger *Logger `ioc:""` // ✗ 未导出,无法注入    Logger *Logger `ioc:""` // ✓ 导出,可以注入}

5. 可选依赖的自动创建

可选依赖不会触发自动创建:

type Service struct {    Cache *Cache `ioc:",optional"`}// 即使 Cache 是结构体类型可以自动创建,// 由于标记为 optional,如果未绑定则保持 nil

6. 上下文容器的优先级

Get 等函数优先使用上下文中的容器:

// 默认容器ioc.Bind(&Config{Port: 8080})// 自定义容器custom := ioc.New()custom.Bind(&Config{Port: 9090})// 上下文ctx := custom.NewContext(context.Background())// 使用上下文中的容器config, _ := ioc.Get[*Config](ctx)fmt.Println((*config).Port) // 9090 (来自自定义容器)// 不使用上下文config2, _ := ioc.Get[*Config](context.Background())fmt.Println((*config2).Port) // 8080 (来自默认容器)

相关链接

Navigate Select esc Close search