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 的核心组件,负责管理服务的注册、创建和生命周期。容器提供两种使用方式:
- 默认容器:通过包级函数使用全局容器
- 自定义容器:创建独立的容器实例
// 使用默认容器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) // DEBUGfmt.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) // 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) // SecondaryGetFrom - 从指定容器获取
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 intCall2 - 双返回值调用
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 = false4. 使用接口而非具体类型
// 定义接口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 Database6. 为测试创建子容器
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,如果未绑定则保持 nil6. 上下文容器的优先级
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 (来自默认容器)