跳到主要内容

ioc - 控制反转容器

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

安装

go get -u go-slim.dev/ioc

快速开始

package main

import (
"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) // DEBUG
fmt.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) // INFO
fmt.Println(svc.DB.Host) // localhost
fmt.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) // true
fmt.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) // Primary
fmt.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 main

import (
"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 main

import (
"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 service

import (
"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 main

import (
"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 main

import "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 Database
type 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 (来自默认容器)

相关链接