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 的核心组件,负责管理服务的注册、创建和生命周期。容器提供两种使用方式:
- 默认容器:通过包级函数使用全局容器
- 自定义容器:创建独立的容器实例
// 使用默认容器
ioc.Bind(&MyService{})
// 创建自定义容器
container := ioc.New()
container.Bind(&MyService{})
绑定类型
IoC 容器支持三种绑定方式:
- 实例绑定:绑定已创建的实例
- 命名绑定:使用名称区分同一类型的多个实现
- 工厂绑定:注册工厂函数延迟创建实例
解析顺序
当请求依赖时,容器按以下顺序查找:
- 实例缓存中的精确类型匹配
- 工厂函数中的精确类型匹配
- 类型兼容的实例(实现接口或可赋值)
- 类型兼容的工厂(实现接口或可赋值)
- 父容器(如果存在)
- 自动创建(仅限结构体和结构体指针)
基本用法
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() T或func() (T, error) - 有参数:
func(dep1 T1, dep2 T2) T或func(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 (来自默认容器)