框架原理

mcube 框架是完全基于 IoC(控制反转)容器构建的微服务开发框架。IoC 不仅仅是一个功能特性,而是贯穿整个框架设计的核心架构理念。

框架通过轻量级、高性能的依赖注入容器,提供:

  • 🏗️ 统一的对象管理:所有组件(数据库、缓存、HTTP服务等)都通过 IoC 容器管理
  • 🔄 自动依赖解析:组件之间的依赖关系自动注入,无需手动初始化
  • 📦 命名空间隔离:按优先级和职责划分的多层次对象管理
  • 生命周期控制:统一管理对象的创建、初始化和销毁流程
  • 🎯 配置驱动开发:通过配置文件驱动对象的创建和装配

📋 目录

简介

什么是 IoC?

IoC(Inversion of Control,控制反转) 是一种设计模式,它将对象的创建和依赖关系的管理从应用代码中分离出来,交给容器来统一管理。这是 mcube 框架的核心架构基础。

在 mcube 框架中,IoC 容器的作用:

  • 解耦代码:组件之间不直接依赖,通过容器自动装配
  • 集中管理:所有框架组件和业务对象统一由容器管理
  • 提高可测试性:便于进行单元测试和依赖替换
  • 标准化开发:提供统一的组件注册和使用方式

为什么 mcube 基于 IoC?

mcube 选择以 IoC 作为框架核心的原因:

1. 微服务应用的复杂依赖管理

在构建 Go 微服务应用时,面临的挑战:

// ❌ 传统方式:手动管理依赖顺序,容易出错
func main() {
    // 必须按正确顺序初始化
    config := loadConfig()
    db := initDB(config.DB)
    cache := initRedis(config.Redis)
    userRepo := NewUserRepo(db)
    userService := NewUserService(userRepo, cache)
    httpServer := NewHTTPServer(userService)
    // ...依赖层层嵌套,维护困难
}

// ✅ IoC 方式:容器自动解析依赖关系
func main() {
    // 组件只需注册,容器自动处理依赖和初始化顺序
    ioc.Controller().Registry(&UserController{})
    ioc.Start()
}

2. 统一的框架扩展机制

通过 IoC 容器,mcube 提供了标准化的组件注册机制:

  • 框架内置组件:HTTP/gRPC 服务器、数据库、缓存等都注册到 IoC 容器
  • 第三方组件:可以无缝集成到框架的生命周期管理中
  • 业务对象:Controller、Service、Repository 统一管理

3. 配置驱动的灵活性

IoC 容器与配置系统深度集成,支持:

# 通过配置文件控制对象的创建和行为
[app]
name = "my-service"

[http]
enabled = true
port = 8080

[grpc]
enabled = false  # 可以通过配置开关功能模块

4. 优雅的生命周期管理

容器统一管理所有对象的生命周期:

  1. Config 阶段:加载配置对象
  2. Resource 阶段:初始化基础资源(数据库、缓存)
  3. Service 阶段:初始化业务服务
  4. Controller 阶段:初始化 API 控制器
  5. 启动和停止:按逆序优雅关闭

总结: mcube 框架将 IoC 作为底层基础设施,所有框架功能都建立在 IoC 容器之上,这使得框架具有高度的可扩展性、可测试性和可维护性。

核心特性

✨ 命名空间隔离(框架分层的基础)

IoC 容器内置 4 个命名空间,对应框架的不同层次,按初始化优先级排序:

命名空间优先级框架层次用途说明典型应用
configs99配置层最先加载,为其他层提供配置数据库配置、应用配置
default9资源层基础设施和工具日志、缓存、数据库连接
controllers0业务层业务逻辑处理Service 层业务逻辑
apis-99接口层对外暴露的 APIHTTP/gRPC 接口实现

分层加载流程:

配置层(configs) → 资源层(default) → 业务层(controllers) → 接口层(apis) ↓ ↓ ↓ ↓ 加载配置 初始化资源 初始化业务服务 启动API服务 (application.toml) (MySQL/Redis) (UserService) (HTTP/gRPC)

🔄 自动依赖注入(框架的核心能力)

通过结构体标签(struct tag)声明依赖,IoC 容器自动装配,无需手动初始化:

// API 层自动注入 Service 层依赖
type UserAPI struct {
    ioc.ObjectImpl  // 继承 IoC 基础对象
    
    // 自动从 controllers 命名空间注入 UserService
    UserService service.UserService `ioc:"autowire=true;namespace=controllers"`
}

// Service 层自动注入 Repository 层依赖
type UserService struct {
    ioc.ObjectImpl
    
    // 自动从 default 命名空间注入数据库连接
    DB *gorm.DB `ioc:"autowire=true;namespace=default;name=db"`
}

依赖注入的优势:

  • 零手动初始化:不需要写任何 New() 函数
  • 跨层依赖管理:自动处理不同命名空间之间的依赖关系
  • 测试友好:方便 Mock 依赖进行单元测试
  • 循环依赖检测:容器自动检测并报告循环依赖问题

⚡ 生命周期管理(框架启停流程)

IoC 容器统一管理所有对象的生命周期,确保框架组件按正确顺序启动和关闭:

核心生命周期方法:

  • Init(): 对象初始化(构造后立即调用)
  • Priority(): 同一命名空间内的启动顺序控制
  • Close(ctx): 优雅关闭(按优先级倒序执行)

扩展生命周期钩子:

  • PostConfig: 配置加载后触发
  • PreInit/PostInit: 对象初始化前后触发
  • PreStop/PostStop: 对象停止前后触发

框架启动流程示例:

1. 加载配置对象 (configs 命名空间) └─ ApplicationConfig.Init() 2. 初始化基础资源 (default 命名空间) ├─ MySQL.Init() ├─ Redis.Init() └─ Cache.Init() 3. 初始化业务服务 (controllers 命名空间) ├─ UserService.Init() └─ OrderService.Init() 4. 启动 API 服务器 (apis 命名空间) ├─ HTTPServer.Init() → 启动 HTTP 服务 └─ GRPCServer.Init() → 启动 gRPC 服务 停止时按倒序执行 Close()

📦 配置驱动开发

框架的配置系统与 IoC 容器深度集成,支持配置驱动的对象管理:

支持的配置格式:

  • TOMLYAMLJSON 配置文件
  • ✅ 从环境变量加载配置
  • ✅ 配置优先级:环境变量 > 配置文件

配置对象自动注入:

// 1. 定义配置对象(注册到 configs 命名空间)
type MySQLConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host"`
    Port     int    `toml:"port"`
    Database string `toml:"database"`
}

// 2. 其他对象自动注入配置
type UserRepository struct {
    ioc.ObjectImpl
    // IoC 容器自动注入配置并初始化数据库连接
    Config *MySQLConfig `ioc:"autowire=true;namespace=configs"`
}

框架通过配置文件即可控制整个应用的行为:

[app]
name = "my-service"

[mysql]
host = "localhost"
port = 3306

[http]
enabled = true  # 开启 HTTP 服务

[grpc]
enabled = false # 关闭 gRPC 服务

🔍 依赖可视化(理解框架架构)

IoC 容器可以自动分析和可视化对象之间的依赖关系,帮助开发者理解框架的整体架构:

# 生成依赖关系图
go run main.go analyze-deps --output deps.dot

# 查看容器中注册的所有对象
go run main.go list-objects

依赖图示例:

apis.UserAPI └─> controllers.UserService ├─> default.MySQL └─> default.Redis

📌 版本控制与扩展性

支持对象版本管理,便于框架升级和功能扩展:

// 注册不同版本的对象
ioc.Api().Registry("user", &UserAPIv1{}, "v1.0.0")
ioc.Api().Registry("user", &UserAPIv2{}, "v2.0.0")

// 使用指定版本
userAPI := ioc.Api().Get("user", "v2.0.0")

框架扩展性:

  • ✅ 可以无缝集成第三方组件到 IoC 容器
  • ✅ 支持插件化开发,动态加载模块
  • ✅ 便于实现微服务的多版本共存

快速开始

安装

go get github.com/infraboard/mcube/v2

基于 IoC 的开发流程

下面通过一个完整示例,展示 mcube 框架基于 IoC 的开发流程:

┌─────────────────────────────────────────────────┐ │ 基于 IoC 的 mcube 开发流程 │ ├─────────────────────────────────────────────────┤ │ 1. 定义业务对象 (Service/Controller) │ │ 2. 注册到 IoC 容器 (init() 函数) │ │ 3. 声明依赖关系 (struct tag) │ │ 4. 启动框架,IoC 自动装配和初始化 │ └─────────────────────────────────────────────────┘

第一个示例:Hello World

步骤1:定义业务服务层 (Controller)

package impl

import (
    "github.com/infraboard/mcube/v2/ioc"
)

func init() {
    // ✅ 注册到 IoC 容器的 controllers 命名空间
    // IoC 容器会自动管理这个对象的生命周期
    ioc.Controller().Registry(&HelloService{})
}

type HelloService struct {
    ioc.ObjectImpl  // 继承 IoC 基础对象,获得生命周期支持
}

// Init() 由 IoC 容器在适当时机自动调用
func (s *HelloService) Init() error {
    // 在这里进行初始化逻辑(如果需要)
    return nil
}

func (s *HelloService) Hello(name string) string {
    return "Hello, " + name + "!"
}

// Name() 定义对象在 IoC 容器中的名称
func (s *HelloService) Name() string {
    return "hello"
}

步骤2:定义 API 层并声明依赖 (API)

package api

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/infraboard/mcube/v2/ioc"
    ioc_gin "github.com/infraboard/mcube/v2/ioc/config/gin"
    "your-project/impl"
)

func init() {
    // ✅ 注册到 IoC 容器的 apis 命名空间
    ioc.Api().Registry(&HelloAPI{})
}

type HelloAPI struct {
    ioc.ObjectImpl
    
    // ✅ 通过 struct tag 声明依赖,IoC 容器自动注入
    // 无需手动初始化 HelloService,容器会自动装配
    svc *impl.HelloService `ioc:"autowire=true;namespace=controllers;name=hello"`
}

func (h *HelloAPI) Init() error {
    // 此时 h.svc 已经被 IoC 容器自动注入
    
    // 注册 HTTP 路由
    router := ioc_gin.ObjectRouter(h)
    router.GET("/hello", h.HandleHello)
    return nil
}

func (h *HelloAPI) HandleHello(c *gin.Context) {
    name := c.Query("name")
    // 使用自动注入的 Service
    c.JSON(http.StatusOK, gin.H{
        "message": h.svc.Hello(name),
    })
}

func (h *HelloAPI) Name() string {
    return "hello"
}

步骤3:启动应用,IoC 自动管理一切

package main

import (
    "context"
    "github.com/infraboard/mcube/v2/ioc/server"
    
    // ✅ 导入模块,触发 init() 函数,将对象注册到 IoC 容器
    _ "your-project/impl"
    _ "your-project/api"
)

func main() {
    // ✅ 启动框架,IoC 容器会:
    // 1. 自动解析依赖关系
    // 2. 按命名空间优先级初始化对象
    // 3. 自动注入依赖
    // 4. 调用 Init() 方法
    // 5. 启动 HTTP/gRPC 服务器
    err := server.Run(context.Background())
    if err != nil {
        panic(err)
    }
}

运行效果

$ go run main.go
# IoC 容器启动日志:
# [IoC] Loading configs namespace...
# [IoC] Loading default namespace...
# [IoC] Loading controllers namespace...
# [IoC]   -> Registered: hello (HelloService)
# [IoC] Loading apis namespace...
# [IoC]   -> Registered: hello (HelloAPI)
# [IoC]   -> Autowired: HelloAPI.svc <- HelloService
# [IoC] Starting HTTP server on :8080...

# 访问 http://localhost:8080/hello?name=World
# 返回: {"message": "Hello, World!"}

关键点总结:

  • 零手动初始化:不需要写 New() 函数或手动创建对象
  • 自动依赖注入:通过 struct tag 声明,IoC 自动装配
  • 分层管理:业务层和 API 层自动分离到不同命名空间
  • 生命周期自动管理:Init() 按顺序调用,Close() 自动倒序执行

核心概念

理解以下核心概念,才能充分发挥 mcube 框架基于 IoC 的优势:

1️⃣ 命名空间(Namespace)- 框架的分层基础

命名空间是 IoC 容器中对象的逻辑分组,对应框架的不同层次,有不同的初始化优先级:

ioc.Config()      // 配置层,最先初始化(优先级 99)
ioc.Default()     // 资源层,基础设施(优先级 9)
ioc.Controller()  // 业务层,Service 逻辑(优先级 0)
ioc.Api()         // 接口层,API 暴露(优先级 -99)

框架分层架构:

┌─────────────────────────────────────────────┐ │ 命名空间层次结构(按初始化顺序) │ ├─────────────────────────────────────────────┤ │ configs (99) ← 配置层 │ │ └─ application.toml 配置加载 │ │ │ │ default (9) ← 资源层 │ │ ├─ MySQL 连接池 │ │ ├─ Redis 客户端 │ │ └─ 日志、缓存等基础设施 │ │ │ │ controllers (0) ← 业务层 │ │ ├─ UserService │ │ ├─ OrderService │ │ └─ 业务逻辑处理 │ │ │ │ apis (-99) ← 接口层 │ │ ├─ HTTP API (Gin) │ │ ├─ gRPC API │ │ └─ 对外接口暴露 │ └─────────────────────────────────────────────┘

初始化顺序:configs → default → controllers → apis
关闭顺序:apis → controllers → default → configs(倒序确保资源正确释放)

2️⃣ 对象(Object)- IoC 管理的基本单元

对象是注册到 IoC 容器的组件,必须实现 Object 接口:

type Object interface {
    Init() error                  // IoC 容器调用:初始化对象
    Name() string                 // 对象名称
    Version() string              // 版本号(默认1.0.0)
    Priority() int                // 优先级(同命名空间内)
    Close(ctx context.Context)    // 优雅关闭
    Meta() ObjectMeta             // 元数据
}

最简实现:继承 ioc.ObjectImpl 获得默认实现,只需覆写需要的方法。

3️⃣ 依赖注入(Autowire)

通过结构体标签声明依赖,容器自动装配:

type UserAPI struct {
    // 自动注入控制器
    UserSvc UserService `ioc:"autowire=true;namespace=controllers"`
    
    // 注入指定版本
    Cache CacheService `ioc:"autowire=true;namespace=default;version=2.0.0"`
}

标签说明

  • autowire=true: 启用自动注入
  • namespace=xxx: 指定从哪个命名空间获取
  • version=x.x.x: 指定对象版本(可选)

4️⃣ 生命周期(Lifecycle)

完整的对象生命周期:

注册 → 配置加载 → 依赖注入 → 初始化 → 运行 → 关闭 ↓ ↓ ↓ ↓ ↓ ↓ Registry Load Autowire Init Running Close

生命周期钩子(可选实现):

  • OnPostConfig(): 配置加载后
  • OnPreInit(): 初始化前
  • OnPostInit(): 初始化后
  • OnPreStop(ctx): 关闭前
  • OnPostStop(ctx): 关闭后

命名空间详解

📌 Config命名空间(优先级99)

用途:存放各种配置对象,最先初始化

典型应用

  • 数据库配置
  • Redis配置
  • 应用全局配置

示例

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    ioc.Config().Registry(&DatabaseConfig{})
}

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host" env:"DB_HOST"`
    Port     int    `toml:"port" env:"DB_PORT"`
    Database string `toml:"database" env:"DB_NAME"`
}

func (c *DatabaseConfig) Init() error {
    // 配置验证
    if c.Host == "" {
        return fmt.Errorf("database host is required")
    }
    return nil
}

🔧 Default命名空间(优先级9)

用途:存放工具类和基础组件

典型应用

  • 数据库连接(GORM、MongoDB)
  • 缓存客户端(Redis)
  • 日志组件
  • 消息队列客户端

示例

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    ioc.Default().Registry(&RedisClient{})
}

type RedisClient struct {
    ioc.ObjectImpl
    Config *RedisConfig `ioc:"autowire=true;namespace=configs"`
    client *redis.Client
}

func (r *RedisClient) Init() error {
    r.client = redis.NewClient(&redis.Options{
        Addr: r.Config.Addr,
    })
    return r.client.Ping(context.Background()).Err()
}

func (r *RedisClient) Close(ctx context.Context) {
    if r.client != nil {
        r.client.Close()
    }
}

🎮 Controller命名空间(优先级0)

用途:存放业务逻辑控制器(Service层)

典型应用

  • 业务服务实现
  • 领域模型
  • 业务逻辑处理

示例

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    ioc.Controller().Registry(&UserService{})
}

type UserService struct {
    ioc.ObjectImpl
    DB    *gorm.DB      `ioc:"autowire=true;namespace=default"`
    Cache *RedisClient `ioc:"autowire=true;namespace=default"`
}

func (s *UserService) Init() error {
    // 初始化业务逻辑
    return nil
}

func (s *UserService) GetUser(id string) (*User, error) {
    // 业务实现
    var user User
    err := s.DB.First(&user, "id = ?", id).Error
    return &user, err
}

🌐 Api命名空间(优先级-99)

用途:存放API处理器(HTTP/gRPC接口层),最后初始化

典型应用

  • HTTP接口处理器
  • gRPC服务实现
  • WebSocket处理器

示例

import (
    "github.com/infraboard/mcube/v2/ioc"
    ioc_gin "github.com/infraboard/mcube/v2/ioc/config/gin"
)

func init() {
    ioc.Api().Registry(&UserAPI{})
}

type UserAPI struct {
    ioc.ObjectImpl
    UserService *UserService `ioc:"autowire=true;namespace=controllers"`
}

func (h *UserAPI) Name() string {
    return "user"  // API路径前缀: /api/v1/user
}

func (h *UserAPI) Init() error {
    router := ioc_gin.ObjectRouter(h)
    router.GET("/:id", h.GetUser)
    return nil
}

func (h *UserAPI) GetUser(c *gin.Context) {
    id := c.Param("id")
    user, err := h.UserService.GetUser(id)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    c.JSON(200, user)
}

🔄 初始化流程

1. [configs] 加载所有配置对象 ↓ 2. [default] 初始化工具类(DB、Redis等) ↓ 3. [controllers] 初始化业务控制器 ↓ 4. [apis] 注册API路由 ↓ 5. 应用启动

对象注册

基本注册

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    // 注册到指定命名空间
    ioc.Controller().Registry(&MyService{})
}

type MyService struct {
    ioc.ObjectImpl
}

实现Object接口

有两种方式实现Object接口:

方式1:继承ObjectImpl(推荐)

type MyService struct {
    ioc.ObjectImpl  // 获得默认实现
}

// 只需覆写需要的方法
func (s *MyService) Init() error {
    // 自定义初始化逻辑
    return nil
}

func (s *MyService) Name() string {
    return "my-service"  // 自定义名称
}

方式2:完全自定义实现

type MyService struct {
    // 不继承ObjectImpl
}

// 必须实现所有接口方法
func (s *MyService) Init() error { return nil }
func (s *MyService) Name() string { return "my-service" }
func (s *MyService) Version() string { return "1.0.0" }
func (s *MyService) Priority() int { return 0 }
func (s *MyService) Close(ctx context.Context) {}
func (s *MyService) Meta() ioc.ObjectMeta { return ioc.DefaultObjectMeta() }

对象命名规则

对象名称用于标识和获取对象:

// 1. 默认名称:包名.类型名
type UserService struct {
    ioc.ObjectImpl
}
// 名称: *impl.UserService

// 2. 自定义名称(推荐)
func (s *UserService) Name() string {
    return "user-service"  // 名称: user-service
}

对象版本控制

支持同一对象的多个版本:

type CacheV1 struct {
    ioc.ObjectImpl
}

func (c *CacheV1) Version() string {
    return "1.0.0"
}

type CacheV2 struct {
    ioc.ObjectImpl
}

func (c *CacheV2) Version() string {
    return "2.0.0"
}

func init() {
    ioc.Default().Registry(&CacheV1{})
    ioc.Default().Registry(&CacheV2{})  // 同名不同版本
}

优先级控制

控制同一命名空间内的初始化顺序:

type DatabaseService struct {
    ioc.ObjectImpl
}

func (d *DatabaseService) Priority() int {
    return 100  // 数字越大越先初始化
}

type CacheService struct {
    ioc.ObjectImpl
}

func (c *CacheService) Priority() int {
    return 50  // 在Database之后初始化
}

批量注册

func init() {
    ioc.Controller().RegistryAll(
        &UserService{},
        &OrderService{},
        &ProductService{},
    )
}

对象元数据

为对象添加额外信息:

type MyAPI struct {
    ioc.ObjectImpl
}

func (a *MyAPI) Meta() ioc.ObjectMeta {
    return ioc.ObjectMeta{
        CustomPathPrefix: "/custom/path",  // 自定义API路径前缀
        Extra: map[string]string{
            "description": "My API Handler",
            "author":      "your-name",
        },
    }
}

依赖注入

使用标签自动注入(推荐)

通过结构体标签声明依赖,容器自动装配:

type UserAPI struct {
    ioc.ObjectImpl
    
    // 基本注入:从controllers命名空间注入
    UserSvc *UserService `ioc:"autowire=true;namespace=controllers"`
    
    // 指定版本注入
    Cache *CacheService `ioc:"autowire=true;namespace=default;version=2.0.0"`
    
    // 私有字段不会被注入
    logger *log.Logger
}

标签参数说明

参数说明必填示例
autowire是否启用自动注入autowire=true
namespace从哪个命名空间获取namespace=controllers
version对象版本version=1.0.0

手动获取依赖

在Init()方法中手动获取:

type UserAPI struct {
    ioc.ObjectImpl
    userSvc *UserService
}

func (a *UserAPI) Init() error {
    // 方式1:直接Get
    obj := ioc.Controller().Get("user-service")
    a.userSvc = obj.(*UserService)
    
    // 方式2:使用Load(推荐)
    var svc *UserService
    err := ioc.Controller().Load(&svc)
    if err != nil {
        return err
    }
    a.userSvc = svc
    
    return nil
}

指定版本获取

import "github.com/infraboard/mcube/v2/ioc"

// 获取指定版本
obj := ioc.Default().Get("cache-service", ioc.WithVersion("2.0.0"))
cache := obj.(*CacheService)

依赖注入执行时机

1. Registry() 注册对象 ↓ 2. LoadConfig() 加载配置到对象 ↓ 3. Autowire() 自动注入依赖 ← 这里执行注入 ↓ 4. Init() 对象初始化

重要:在 Init() 方法中,所有标签声明的依赖已经注入完成。

循环依赖处理

IOC容器会检测循环依赖:

// ❌ 错误:循环依赖
type ServiceA struct {
    B *ServiceB `ioc:"autowire=true;namespace=controllers"`
}

type ServiceB struct {
    A *ServiceA `ioc:"autowire=true;namespace=controllers"`
}

解决方案

  1. 延迟获取:在使用时再获取
type ServiceA struct {
    ioc.ObjectImpl
}

func (a *ServiceA) GetB() *ServiceB {
    obj := ioc.Controller().Get("service-b")
    return obj.(*ServiceB)
}
  1. 引入中间层:通过接口解耦
type ServiceA struct {
    Handler BHandler `ioc:"autowire=true;namespace=controllers"`
}

type BHandler interface {
    Handle()
}

依赖可选性

当依赖不存在时的处理:

type MyService struct {
    ioc.ObjectImpl
    // 必选依赖:不存在会报错
    DB *gorm.DB `ioc:"autowire=true;namespace=default"`
}

func (s *MyService) Init() error {
    // 可选依赖:手动检查
    obj := ioc.Default().Get("optional-cache")
    if obj != nil {
        s.cache = obj.(*Cache)
    }
    return nil
}

声明依赖关系

当手动获取依赖时,如需在依赖图中展示关系,可实现 DependencyDeclarer 接口:

type MyService struct {
    ioc.ObjectImpl
    cache *Cache  // 手动获取
}

func (s *MyService) Init() error {
    obj := ioc.Default().Get("cache")
    s.cache = obj.(*Cache)
    return nil
}

// 声明依赖关系(用于依赖可视化)
func (s *MyService) Dependencies() []ioc.Dependency {
    return []ioc.Dependency{
        {
            Name:      "cache",
            Namespace: "default",
            Version:   "1.0.0",
        },
    }
}

生命周期管理

初始化顺序

IOC容器按以下顺序初始化对象:

1. 按命名空间优先级:configs(99) → default(9) → controllers(0) → apis(-99) 2. 同一命名空间内按Priority():数字越大越先初始化 3. 同优先级按注册顺序

示例

// configs命名空间:优先级99,最先初始化
type AppConfig struct {
    ioc.ObjectImpl
}
func (c *AppConfig) Priority() int { return 100 }

// default命名空间:优先级9
type Database struct {
    ioc.ObjectImpl
}
func (d *Database) Priority() int { return 50 }

// controllers命名空间:优先级0
type UserService struct {
    ioc.ObjectImpl
}
func (s *UserService) Priority() int { return 0 }

// 初始化顺序:AppConfig → Database → UserService

完整生命周期

┌─────────────────────────────────────────────────────────┐ │ 1. Registry(): 注册对象到容器 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 2. LoadConfig(): 从配置文件/环境变量加载配置 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 3. OnPostConfig(): 配置加载后钩子(可选) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 4. Autowire(): 自动注入依赖 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 5. OnPreInit(): 初始化前钩子(可选) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 6. Init(): 对象初始化 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 7. OnPostInit(): 初始化后钩子(可选) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 8. Running: 应用运行中 │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 9. OnPreStop(): 关闭前钩子(可选) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 10. Close(): 对象关闭(倒序) │ └─────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────┐ │ 11. OnPostStop(): 关闭后钩子(可选) │ └─────────────────────────────────────────────────────────┘

生命周期钩子

实现可选的生命周期钩子接口:

1. PostConfigHook - 配置加载后

type MyService struct {
    ioc.ObjectImpl
    Host string `toml:"host" env:"HOST"`
}

func (s *MyService) OnPostConfig() error {
    // 配置验证
    if s.Host == "" {
        return fmt.Errorf("host is required")
    }
    // 配置预处理
    s.Host = strings.TrimSpace(s.Host)
    return nil
}

2. PreInitHook - 初始化前

func (s *MyService) OnPreInit() error {
    // 准备工作
    log.Println("Preparing to initialize MyService...")
    return nil
}

3. PostInitHook - 初始化后

func (s *MyService) OnPostInit() error {
    // 启动后台任务
    go s.backgroundTask()
    // 注册监听器
    s.registerListeners()
    return nil
}

4. PreStopHook - 关闭前

func (s *MyService) OnPreStop(ctx context.Context) error {
    // 优雅停机检查
    log.Println("Preparing to stop MyService...")
    // 等待请求完成(带超时)
    return s.waitForRequests(ctx)
}

5. PostStopHook - 关闭后

func (s *MyService) OnPostStop(ctx context.Context) error {
    // 最终清理
    log.Println("MyService stopped successfully")
    return nil
}

优雅关闭

容器按倒序关闭对象:

type Database struct {
    ioc.ObjectImpl
    conn *sql.DB
}

func (d *Database) Close(ctx context.Context) {
    if d.conn != nil {
        // 等待连接关闭,但不超过context超时
        d.conn.Close()
    }
}

关闭顺序:apis → controllers → default → configs(与初始化相反)

错误处理

func (s *MyService) Init() error {
    // 初始化失败会阻止应用启动
    if err := s.connect(); err != nil {
        return fmt.Errorf("failed to connect: %w", err)
    }
    return nil
}

func (s *MyService) OnPostInit() error {
    // PostInit失败会记录错误但不阻止启动
    if err := s.warmup(); err != nil {
        log.Printf("warmup failed: %v", err)
        return err
    }
    return nil
}

func (s *MyService) Close(ctx context.Context) {
    // Close不返回error,应该做好容错
    if s.conn != nil {
        _ = s.conn.Close()
    }
}

超时控制

func (s *MyService) Close(ctx context.Context) {
    // 使用context控制关闭超时
    done := make(chan struct{})
    
    go func() {
        s.cleanup()
        close(done)
    }()
    
    select {
    case <-done:
        log.Println("Cleanup completed")
    case <-ctx.Done():
        log.Println("Cleanup timeout")
    }
}

配置管理

配置文件加载

支持 TOMLYAMLJSON 三种格式:

配置文件示例 (etc/application.toml)

[database]
host = "localhost"
port = 3306
database = "myapp"
username = "root"
password = "secret"

[redis]
addr = "localhost:6379"
db = 0

对象定义

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host" json:"host" yaml:"host"`
    Port     int    `toml:"port" json:"port" yaml:"port"`
    Database string `toml:"database" json:"database" yaml:"database"`
    Username string `toml:"username" json:"username" yaml:"username"`
    Password string `toml:"password" json:"password" yaml:"password"`
}

func (c *DatabaseConfig) Name() string {
    return "database"  // 对应配置文件中的[database]节点
}

func init() {
    ioc.Config().Registry(&DatabaseConfig{})
}

启用配置文件加载

import "github.com/infraboard/mcube/v2/ioc/server"

func main() {
    // 方式1:使用默认配置
    server.DefaultConfig.ConfigFile.Enabled = true
    server.DefaultConfig.ConfigFile.Paths = []string{
        "etc/application.toml",
        "etc/application.yaml",  // 支持多个配置文件
    }
    
    server.Run(context.Background())
}

环境变量配置

使用 env 标签声明环境变量映射:

type DatabaseConfig struct {
    ioc.ObjectImpl
    // 优先从环境变量加载,不存在则使用配置文件
    Host     string `toml:"host" env:"DB_HOST"`
    Port     int    `toml:"port" env:"DB_PORT"`
    Database string `toml:"database" env:"DB_NAME"`
    Username string `toml:"username" env:"DB_USER"`
    Password string `toml:"password" env:"DB_PASS"`
}

func init() {
    ioc.Config().Registry(&DatabaseConfig{})
}

使用

# 设置环境变量
export DB_HOST=production-db.example.com
export DB_PORT=5432
export DB_NAME=prod_database

# 启动应用,环境变量会覆盖配置文件
go run main.go

配置加载方式

方式1:通过Server自动加载(推荐)

import "github.com/infraboard/mcube/v2/ioc/server"

func main() {
    server.DefaultConfig.ConfigFile.Enabled = true
    server.DefaultConfig.ConfigFile.Paths = []string{"etc/app.toml"}
    server.Run(context.Background())
}

方式2:手动加载

import "github.com/infraboard/mcube/v2/ioc"

func main() {
    // 从文件加载
    content, _ := os.ReadFile("etc/app.toml")
    ioc.Config().LoadFromFileContent(content)
    
    // 从环境变量加载
    ioc.Config().LoadFromEnv("APP")  // 前缀为APP_的环境变量
    
    // 初始化所有对象
    ioc.InitAll()
}

配置优先级

配置加载遵循以下优先级(高到低):

1. 环境变量(env标签) ↓ 2. 配置文件(toml/yaml/json标签) ↓ 3. 字段默认值

示例

type Config struct {
    ioc.ObjectImpl
    Host string `toml:"host" env:"HOST"`
    Port int    `toml:"port" env:"PORT"`
}

// 字段默认值
func NewConfig() *Config {
    return &Config{
        Host: "localhost",  // 默认值
        Port: 8080,         // 默认值
    }
}

加载顺序

  1. 使用默认值:Host=localhost, Port=8080
  2. 加载配置文件(如果有):覆盖默认值
  3. 加载环境变量(如果有):覆盖配置文件

配置验证

OnPostConfig() 钩子中验证配置:

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host" env:"DB_HOST"`
    Port     int    `toml:"port" env:"DB_PORT"`
    Database string `toml:"database" env:"DB_NAME"`
}

func (c *DatabaseConfig) OnPostConfig() error {
    // 必填项检查
    if c.Host == "" {
        return fmt.Errorf("database host is required")
    }
    
    // 范围检查
    if c.Port <= 0 || c.Port > 65535 {
        return fmt.Errorf("invalid port: %d", c.Port)
    }
    
    // 格式检查
    if c.Database == "" {
        return fmt.Errorf("database name is required")
    }
    
    return nil
}

多环境配置

方式1:不同配置文件

// 根据环境变量选择配置文件
env := os.Getenv("APP_ENV")
if env == "" {
    env = "dev"
}

configFile := fmt.Sprintf("etc/application-%s.toml", env)
server.DefaultConfig.ConfigFile.Paths = []string{configFile}

方式2:配置覆盖

# etc/application.toml (基础配置)
[database]
host = "localhost"
port = 3306

# etc/application-prod.toml (生产环境覆盖)
[database]
host = "prod-db.example.com"
port = 5432
server.DefaultConfig.ConfigFile.Paths = []string{
    "etc/application.toml",      // 基础配置
    "etc/application-prod.toml", // 环境特定配置(覆盖)
}

配置热更新

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host string `toml:"host" env:"DB_HOST"`
    mu   sync.RWMutex
}

func (c *DatabaseConfig) GetHost() string {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.Host
}

func (c *DatabaseConfig) UpdateHost(host string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.Host = host
}

注意:IOC容器暂不支持自动配置热更新,需要手动实现。

敏感信息处理

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host" env:"DB_HOST"`
    Password string `toml:"password" env:"DB_PASS"`
}

func (c *DatabaseConfig) OnPostConfig() error {
    // 从密钥管理系统加载敏感信息
    if c.Password == "" {
        password, err := loadPasswordFromVault()
        if err != nil {
            return err
        }
        c.Password = password
    }
    return nil
}

func (c *DatabaseConfig) String() string {
    // 避免日志泄露密码
    return fmt.Sprintf("DB{host=%s, password=***}", c.Host)
}

高级特性

对象版本控制

支持语义化版本(Semantic Versioning):

type CacheV1 struct {
    ioc.ObjectImpl
}

func (c *CacheV1) Name() string {
    return "cache-service"
}

func (c *CacheV1) Version() string {
    return "1.0.0"
}

type CacheV2 struct {
    ioc.ObjectImpl
}

func (c *CacheV2) Name() string {
    return "cache-service"
}

func (c *CacheV2) Version() string {
    return "2.0.0"
}

func init() {
    ioc.Default().Registry(&CacheV1{})
    ioc.Default().Registry(&CacheV2{})
}

使用指定版本

// 注入时指定版本
type MyService struct {
    CacheV1 *CacheV1 `ioc:"autowire=true;namespace=default;version=1.0.0"`
    CacheV2 *CacheV2 `ioc:"autowire=true;namespace=default;version=2.0.0"`
}

// 获取时指定版本
cache := ioc.Default().Get("cache-service", ioc.WithVersion("2.0.0"))

对象覆盖与替换

允许覆盖已注册的对象(用于测试或自定义实现):

// 原始实现
type DefaultEmailService struct {
    ioc.ObjectImpl
}

func (s *DefaultEmailService) Send(to, subject, body string) error {
    // 真实邮件发送
    return smtp.SendEmail(to, subject, body)
}

// 测试环境替换为Mock实现
type MockEmailService struct {
    ioc.ObjectImpl
}

func (s *MockEmailService) Send(to, subject, body string) error {
    // Mock实现:只记录日志
    log.Printf("Mock email sent to %s", to)
    return nil
}

func init() {
    if os.Getenv("ENV") == "test" {
        // 覆盖原实现
        ioc.Default().Registry(&MockEmailService{})
    } else {
        ioc.Default().Registry(&DefaultEmailService{})
    }
}

依赖可视化

查看对象列表和依赖关系:

import "github.com/infraboard/mcube/v2/ioc"

func main() {
    // 列出所有已注册对象
    objects := ioc.Default().List()
    for _, name := range objects {
        fmt.Println(name)
    }
    
    // 统计对象数量
    count := ioc.Controller().Len()
    fmt.Printf("Total controllers: %d\n", count)
    
    // 遍历对象
    ioc.Api().ForEach(func(obj *ioc.ObjectWrapper) {
        fmt.Printf("API: %s (v%s)\n", obj.Name, obj.Version)
    })
}

生成依赖图(参考 DEPENDENCY_VISUALIZATION.md):

# 使用工具生成依赖关系图
go run tools/dependency-viz/main.go

条件注册

根据条件决定是否注册对象:

func init() {
    // 只在特性开关启用时注册
    if featureEnabled("new-cache") {
        ioc.Default().Registry(&NewCacheService{})
    } else {
        ioc.Default().Registry(&OldCacheService{})
    }
    
    // 根据环境注册
    if os.Getenv("ENABLE_METRICS") == "true" {
        ioc.Default().Registry(&MetricsCollector{})
    }
}

自定义命名空间

创建自定义命名空间:

import "github.com/infraboard/mcube/v2/ioc"

// 创建自定义命名空间
func TaskNamespace() ioc.StoreUser {
    return ioc.DefaultStore.Namespace("tasks").
        SetPriority(-50)  // 设置优先级
}

func init() {
    // 注册到自定义命名空间
    TaskNamespace().Registry(&CronTask{})
}

对象工厂模式

使用工厂函数创建对象:

type ConnectionPool struct {
    ioc.ObjectImpl
    config *PoolConfig `ioc:"autowire=true;namespace=configs"`
    pool   *Pool
}

func (c *ConnectionPool) Init() error {
    // 根据配置创建连接池
    c.pool = NewPool(c.config.MaxConnections, c.config.Timeout)
    return c.pool.Connect()
}

func NewConnectionPool() *ConnectionPool {
    return &ConnectionPool{
        // 初始化默认值
    }
}

func init() {
    ioc.Default().Registry(NewConnectionPool())
}

延迟初始化

某些对象可能需要延迟初始化:

type HeavyService struct {
    ioc.ObjectImpl
    client *heavy.Client
    once   sync.Once
}

func (s *HeavyService) Init() error {
    // 不在这里初始化重资源
    return nil
}

func (s *HeavyService) GetClient() *heavy.Client {
    // 首次使用时才初始化
    s.once.Do(func() {
        s.client = heavy.NewClient()
    })
    return s.client
}

对象分组

使用Meta元数据对对象分组:

type UserAPI struct {
    ioc.ObjectImpl
}

func (a *UserAPI) Meta() ioc.ObjectMeta {
    meta := ioc.DefaultObjectMeta()
    meta.Extra["group"] = "user-module"
    meta.Extra["version"] = "v2"
    return meta
}

// 查询特定分组的对象
ioc.Api().ForEach(func(obj *ioc.ObjectWrapper) {
    if obj.Meta().Extra["group"] == "user-module" {
        fmt.Println(obj.Name)
    }
})

健康检查集成

为对象添加健康检查:

type DatabaseService struct {
    ioc.ObjectImpl
    db *sql.DB
}

func (s *DatabaseService) HealthCheck() error {
    return s.db.Ping()
}

// 在健康检查端点中使用
func healthCheckHandler(c *gin.Context) {
    var unhealthy []string
    
    ioc.Default().ForEach(func(obj *ioc.ObjectWrapper) {
        if checker, ok := obj.Value.(interface{ HealthCheck() error }); ok {
            if err := checker.HealthCheck(); err != nil {
                unhealthy = append(unhealthy, obj.Name)
            }
        }
    })
    
    if len(unhealthy) > 0 {
        c.JSON(500, gin.H{"status": "unhealthy", "services": unhealthy})
    } else {
        c.JSON(200, gin.H{"status": "healthy"})
    }
}

事件监听

监听对象生命周期事件:

type EventListener struct {
    ioc.ObjectImpl
}

func (l *EventListener) OnPostInit() error {
    log.Println("All objects initialized")
    // 发送初始化完成事件
    publishEvent("ioc.initialized")
    return nil
}

func init() {
    ioc.Default().Registry(&EventListener{})
}

最佳实践

项目结构建议

推荐的项目结构:

myapp/ ├── main.go # 应用入口 ├── etc/ │ ├── application.toml # 配置文件 │ └── application-prod.toml ├── configs/ # 配置对象 │ ├── database.go │ └── redis.go ├── apps/ # 业务模块 │ ├── user/ │ │ ├── interface.go # 接口定义 │ │ ├── impl/ # 业务实现 (controllers) │ │ │ └── impl.go │ │ └── api/ # API层 (apis) │ │ └── http.go │ └── order/ │ ├── interface.go │ ├── impl/ │ └── api/ └── pkg/ # 工具包 (default) ├── database/ ├── cache/ └── logger/

示例代码结构

// apps/user/interface.go - 定义接口
package user

type Service interface {
    GetUser(id string) (*User, error)
    CreateUser(user *User) error
}

// apps/user/impl/impl.go - 实现业务逻辑
package impl

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    ioc.Controller().Registry(&UserServiceImpl{})
}

type UserServiceImpl struct {
    ioc.ObjectImpl
    DB *gorm.DB `ioc:"autowire=true;namespace=default"`
}

func (s *UserServiceImpl) Name() string {
    return "user"
}

// apps/user/api/http.go - 实现HTTP接口
package api

import "github.com/infraboard/mcube/v2/ioc"

func init() {
    ioc.Api().Registry(&UserAPI{})
}

type UserAPI struct {
    ioc.ObjectImpl
    UserSvc user.Service `ioc:"autowire=true;namespace=controllers"`
}

func (a *UserAPI) Name() string {
    return "user"
}

命名约定

1. 对象命名

// ✅ 推荐:使用小写连字符
func (s *UserService) Name() string {
    return "user-service"
}

// ❌ 避免:使用Go类型名
func (s *UserService) Name() string {
    return "*impl.UserServiceImpl"  // 太长且不直观
}

2. 配置节点命名

# ✅ 推荐:与对象Name()一致
[database]
host = "localhost"

[redis-cache]
addr = "localhost:6379"
func (c *DatabaseConfig) Name() string {
    return "database"  // 匹配配置节点
}

依赖管理原则

1. 依赖方向

configs → default → controllers → apis ↑ ↑ ↑ ↑ 配置 工具类 业务层 接口层
  • ✅ apis可以依赖controllers
  • ✅ controllers可以依赖default
  • ❌ default不应该依赖controllers
  • ❌ 避免跨层依赖

2. 接口依赖

// ✅ 推荐:依赖接口
type UserAPI struct {
    UserSvc user.Service `ioc:"autowire=true;namespace=controllers"`
}

// ❌ 避免:直接依赖实现
type UserAPI struct {
    UserSvc *impl.UserServiceImpl `ioc:"autowire=true;namespace=controllers"`
}

3. 避免循环依赖

// ❌ 错误:循环依赖
type ServiceA struct {
    B *ServiceB `ioc:"autowire=true"`
}
type ServiceB struct {
    A *ServiceA `ioc:"autowire=true"`
}

// ✅ 解决:使用接口或延迟获取
type ServiceA struct {
    ioc.ObjectImpl
}
func (a *ServiceA) GetB() *ServiceB {
    return ioc.Controller().Get("service-b").(*ServiceB)
}

错误处理

1. Init()中的错误

func (s *DatabaseService) Init() error {
    db, err := sql.Open("mysql", s.dsn)
    if err != nil {
        // ✅ 返回带上下文的错误
        return fmt.Errorf("failed to open database: %w", err)
    }
    
    // ✅ 测试连接
    if err := db.Ping(); err != nil {
        return fmt.Errorf("failed to ping database: %w", err)
    }
    
    s.db = db
    return nil
}

2. Close()中的错误处理

func (s *DatabaseService) Close(ctx context.Context) {
    if s.db == nil {
        return  // ✅ 防御性检查
    }
    
    // ✅ Close不返回error,需要自己处理
    if err := s.db.Close(); err != nil {
        log.Printf("failed to close database: %v", err)
    }
}

3. 配置验证

func (c *DatabaseConfig) OnPostConfig() error {
    // ✅ 必填项检查
    if c.Host == "" {
        return fmt.Errorf("database.host is required")
    }
    
    // ✅ 范围检查
    if c.MaxConnections < 1 || c.MaxConnections > 1000 {
        return fmt.Errorf("database.max_connections must be between 1 and 1000")
    }
    
    // ✅ 格式验证
    if _, err := url.Parse(c.DSN); err != nil {
        return fmt.Errorf("invalid database.dsn: %w", err)
    }
    
    return nil
}

测试建议

1. 单元测试

func TestUserService(t *testing.T) {
    // 创建测试用的IOC容器
    testStore := ioc.NewNamespaceStore("test", 0)
    
    // 注册Mock对象
    mockDB := &MockDatabase{}
    testStore.Registry(mockDB)
    
    // 创建待测试对象
    svc := &UserServiceImpl{}
    testStore.Registry(svc)
    
    // 执行依赖注入
    testStore.Autowire()
    
    // 初始化
    if err := svc.Init(); err != nil {
        t.Fatal(err)
    }
    
    // 测试
    user, err := svc.GetUser("user-001")
    assert.NoError(t, err)
    assert.NotNil(t, user)
}

2. 集成测试

func TestMain(m *testing.M) {
    // 设置测试环境
    os.Setenv("ENV", "test")
    os.Setenv("DB_HOST", "localhost")
    
    // 注册测试对象
    ioc.Controller().Registry(&UserServiceImpl{})
    ioc.Api().Registry(&UserAPI{})
    
    // 初始化IOC容器
    if err := ioc.InitAll(); err != nil {
        log.Fatal(err)
    }
    
    // 运行测试
    code := m.Run()
    
    // 清理
    ioc.CloseAll(context.Background())
    
    os.Exit(code)
}

性能优化

1. 避免过度反射

// ✅ 推荐:缓存对象引用
type UserAPI struct {
    ioc.ObjectImpl
    userSvc *UserService
}

func (a *UserAPI) Init() error {
    obj := ioc.Controller().Get("user-service")
    a.userSvc = obj.(*UserService)
    return nil
}

// ❌ 避免:每次都Get
func (a *UserAPI) HandleGetUser(c *gin.Context) {
    svc := ioc.Controller().Get("user-service").(*UserService)  // 每次都反射
    // ...
}

2. 合理设置优先级

// 数据库应该最先初始化
func (d *Database) Priority() int {
    return 100
}

// 依赖数据库的服务优先级较低
func (s *UserService) Priority() int {
    return 50
}

安全建议

1. 敏感信息保护

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host     string `toml:"host"`
    Password string `toml:"password" json:"-"`  // ✅ json标签防止序列化泄露
}

// ✅ 自定义String()方法
func (c *DatabaseConfig) String() string {
    return fmt.Sprintf("DB{host=%s, password=***}", c.Host)
}

2. 配置文件权限

# ✅ 限制配置文件权限
chmod 600 etc/application.toml

# ✅ 不要提交包含敏感信息的配置到Git
echo "etc/*.toml" >> .gitignore

3. 环境变量优先

// ✅ 生产环境使用环境变量而非配置文件
type DatabaseConfig struct {
    Password string `env:"DB_PASSWORD"`  // 优先从环境变量读取
}

日志规范

func (s *UserService) Init() error {
    log.Printf("[IOC] Initializing UserService...")
    
    if err := s.connect(); err != nil {
        log.Printf("[IOC] UserService init failed: %v", err)
        return err
    }
    
    log.Printf("[IOC] UserService initialized successfully")
    return nil
}

func (s *UserService) Close(ctx context.Context) {
    log.Printf("[IOC] Closing UserService...")
    s.disconnect()
    log.Printf("[IOC] UserService closed")
}

API参考

核心接口

Object接口

所有注册到IOC容器的对象必须实现此接口:

type Object interface {
    Init() error                  // 对象初始化
    Name() string                 // 对象名称
    Version() string              // 版本号(默认1.0.0)
    Priority() int                // 优先级(数字越大越先初始化)
    Close(ctx context.Context)    // 优雅关闭
    Meta() ObjectMeta             // 元数据
}

StoreUser接口

用户操作接口,用于注册和获取对象:

type StoreUser interface {
    // 注册对象
    Registry(obj Object) StoreUser
    
    // 批量注册对象
    RegistryAll(objs ...Object) StoreUser
    
    // 获取对象
    Get(name string, opts ...GetOption) Object
    
    // 加载对象到变量
    Load(obj any, opts ...GetOption) error
    
    // 列出所有对象名称
    List() []string
    
    // 对象数量
    Len() int
    
    // 遍历所有对象
    ForEach(fn func(*ObjectWrapper))
}

生命周期钩子接口

可选实现的钩子接口:

// 配置加载后钩子
type PostConfigHook interface {
    Object
    OnPostConfig() error
}

// 初始化前钩子
type PreInitHook interface {
    Object
    OnPreInit() error
}

// 初始化后钩子
type PostInitHook interface {
    Object
    OnPostInit() error
}

// 停止前钩子
type PreStopHook interface {
    Object
    OnPreStop(ctx context.Context) error
}

// 停止后钩子
type PostStopHook interface {
    Object
    OnPostStop(ctx context.Context) error
}

命名空间函数

ioc.Config()

func Config() StoreUser

返回配置命名空间(优先级99),用于注册配置对象。

示例

ioc.Config().Registry(&DatabaseConfig{})

ioc.Default()

func Default() StoreUser

返回默认命名空间(优先级9),用于注册工具类。

示例

ioc.Default().Registry(&DatabaseClient{})

ioc.Controller()

func Controller() StoreUser

返回控制器命名空间(优先级0),用于注册业务控制器。

示例

ioc.Controller().Registry(&UserService{})

ioc.Api()

func Api() StoreUser

返回API命名空间(优先级-99),用于注册API处理器。

示例

ioc.Api().Registry(&UserAPI{})

常用方法

Registry - 注册对象

func (s *NamespaceStore) Registry(obj Object) StoreUser

注册对象到命名空间。

参数

  • obj: 实现Object接口的对象

返回

  • 返回自身,支持链式调用

示例

ioc.Default().Registry(&RedisClient{})

// 链式调用
ioc.Controller().
    Registry(&UserService{}).
    Registry(&OrderService{})

Get - 获取对象

func (s *NamespaceStore) Get(name string, opts ...GetOption) Object

根据名称获取对象。

参数

  • name: 对象名称
  • opts: 可选参数(如版本)

返回

  • Object实例,不存在返回nil

示例

// 获取默认版本
obj := ioc.Default().Get("redis-client")
client := obj.(*RedisClient)

// 获取指定版本
obj := ioc.Default().Get("cache", ioc.WithVersion("2.0.0"))

Load - 加载对象

func (s *NamespaceStore) Load(obj any, opts ...GetOption) error

将对象加载到变量中(通过反射)。

参数

  • obj: 指向对象的指针
  • opts: 可选参数

返回

  • error: 加载失败返回错误

示例

var db *gorm.DB
err := ioc.Default().Load(&db)
if err != nil {
    return err
}

List - 列出对象

func (s *NamespaceStore) List() []string

返回所有已注册对象的名称列表。

示例

objects := ioc.Controller().List()
for _, name := range objects {
    fmt.Println(name)
}

ForEach - 遍历对象

func (s *NamespaceStore) ForEach(fn func(*ObjectWrapper))

遍历所有已注册的对象。

示例

ioc.Api().ForEach(func(obj *ioc.ObjectWrapper) {
    fmt.Printf("API: %s (v%s)\n", obj.Name, obj.Version)
})

配置加载

LoadFromEnv - 从环境变量加载

func (s *NamespaceStore) LoadFromEnv(prefix string) error

从环境变量加载配置。

参数

  • prefix: 环境变量前缀

示例

// 加载APP_为前缀的环境变量
ioc.Config().LoadFromEnv("APP")

// 例如: APP_DATABASE_HOST -> DatabaseConfig.Host

选项函数

WithVersion - 指定版本

func WithVersion(version string) GetOption

获取或注入时指定对象版本。

示例

obj := ioc.Default().Get("cache", ioc.WithVersion("2.0.0"))

工具函数

ObjectUid - 对象唯一标识

func ObjectUid(o *ObjectWrapper) string

返回对象的唯一标识(名称.版本)。

示例

uid := ioc.ObjectUid(wrapper)  // "user-service.1.0.0"

CompareVersion - 版本比较

func CompareVersion(v1, v2 string) int

比较两个语义化版本号。

返回值

  • 1: v1 > v2
  • -1: v1 < v2
  • 0: v1 == v2

示例

result := ioc.CompareVersion("2.0.0", "1.5.0")  // 1
result := ioc.CompareVersion("1.0.0", "2.0.0")  // -1
result := ioc.CompareVersion("1.0.0", "1.0.0")  // 0

标签语法

依赖注入标签语法:

`ioc:"key1=value1;key2=value2;key3=value3"`

支持的参数

参数类型说明示例
autowirebool是否自动注入autowire=true
namespacestring命名空间namespace=controllers
versionstring对象版本version=2.0.0

示例

type MyService struct {
    // 完整标签
    DB *gorm.DB `ioc:"autowire=true;namespace=default;version=1.0.0"`
    
    // 最简标签(使用默认版本)
    Cache *Redis `ioc:"autowire=true;namespace=default"`
}

ObjectWrapper

对象包装器,包含对象及其元信息:

type ObjectWrapper struct {
    Name           string      // 对象名称
    Version        string      // 对象版本
    Value          Object      // 对象实例
    Priority       int         // 优先级
    AllowOverwrite bool        // 是否允许覆盖
    Meta           ObjectMeta  // 元数据
}

ObjectMeta

对象元数据:

type ObjectMeta struct {
    CustomPathPrefix string            // 自定义API路径前缀
    Extra            map[string]string // 扩展字段
}

常见问题

Q1: 为什么我的依赖注入失败了?

A: 检查以下几点:

  1. 标签语法是否正确
// ✅ 正确
DB *gorm.DB `ioc:"autowire=true;namespace=default"`

// ❌ 错误:拼写错误
DB *gorm.DB `ioc:"autowire=ture;namespace=default"`

// ❌ 错误:缺少namespace
DB *gorm.DB `ioc:"autowire=true"`
  1. 对象是否已注册
// 确保对象已注册
func init() {
    ioc.Default().Registry(&DatabaseClient{})
}
  1. 命名空间是否正确
// 确保从正确的命名空间获取
type API struct {
    // 如果Service注册在controllers,必须指定namespace=controllers
    Svc *Service `ioc:"autowire=true;namespace=controllers"`
}
  1. 字段必须是导出字段(公开)
// ✅ 正确:首字母大写
DB *gorm.DB `ioc:"autowire=true;namespace=default"`

// ❌ 错误:私有字段不会注入
db *gorm.DB `ioc:"autowire=true;namespace=default"`

Q2: 循环依赖如何解决?

A: 有三种解决方案:

方案1:延迟获取

type ServiceA struct {
    ioc.ObjectImpl
}

func (a *ServiceA) GetB() *ServiceB {
    return ioc.Controller().Get("service-b").(*ServiceB)
}

方案2:使用接口解耦

type ServiceA struct {
    Handler BHandler `ioc:"autowire=true;namespace=controllers"`
}

type BHandler interface {
    Handle()
}

方案3:重构设计

  • 通常循环依赖表示设计有问题
  • 考虑提取公共逻辑到第三个服务
  • 或者调整依赖方向

Q3: 如何查看所有已注册的对象?

A: 使用List()和ForEach()方法:

// 列出所有对象名称
objects := ioc.Controller().List()
for _, name := range objects {
    fmt.Println(name)
}

// 遍历对象详情
ioc.Controller().ForEach(func(obj *ioc.ObjectWrapper) {
    fmt.Printf("%s (v%s) - Priority: %d\n", 
        obj.Name, obj.Version, obj.Priority)
})

Q4: 如何在单元测试中使用IOC?

A: 创建独立的测试容器:

func TestMyService(t *testing.T) {
    // 方式1:使用独立的命名空间
    testStore := ioc.NewNamespaceStore("test", 0)
    testStore.Registry(&MockDatabase{})
    testStore.Registry(&MyService{})
    testStore.Autowire()
    
    // 方式2:替换全局对象
    original := ioc.Default().Get("database")
    ioc.Default().Registry(&MockDatabase{})
    
    // 测试完成后恢复
    defer func() {
        ioc.Default().Registry(original)
    }()
}

Q5: 对象初始化顺序如何控制?

A: 通过三个维度控制:

  1. 命名空间优先级(最重要)
configs (99)default (9) → controllers (0) → apis (-99)
  1. Priority()方法(同命名空间内)
func (d *Database) Priority() int {
    return 100  // 数字越大越先初始化
}
  1. 注册顺序(同优先级时)
func init() {
    ioc.Default().Registry(&A{})  // 先注册先初始化
    ioc.Default().Registry(&B{})
}

Q6: 配置文件加载失败怎么办?

A: 检查以下几点:

  1. 文件路径是否正确
server.DefaultConfig.ConfigFile.Paths = []string{
    "etc/application.toml",  // 相对于项目根目录
}
  1. 配置节点名称是否匹配
# 配置文件
[database]
host = "localhost"
// 对象Name()必须匹配
func (c *DatabaseConfig) Name() string {
    return "database"  // 必须匹配[database]
}
  1. 标签是否正确
type Config struct {
    // 确保标签格式正确
    Host string `toml:"host" env:"DB_HOST"`
}

Q7: 如何实现配置热更新?

A: IOC容器本身不支持自动热更新,但可以手动实现:

type DatabaseConfig struct {
    ioc.ObjectImpl
    Host string `toml:"host"`
    mu   sync.RWMutex
}

// 线程安全的读取
func (c *DatabaseConfig) GetHost() string {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.Host
}

// 线程安全的更新
func (c *DatabaseConfig) UpdateHost(host string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.Host = host
}

// 监听配置文件变化
func watchConfig() {
    watcher, _ := fsnotify.NewWatcher()
    watcher.Add("etc/application.toml")
    
    for {
        select {
        case event := <-watcher.Events:
            if event.Op&fsnotify.Write == fsnotify.Write {
                reloadConfig()
            }
        }
    }
}

Q8: 多个相同类型的对象如何区分?

A: 使用Name()和Version()区分:

type CachePrimary struct {
    ioc.ObjectImpl
}
func (c *CachePrimary) Name() string {
    return "cache-primary"
}

type CacheSecondary struct {
    ioc.ObjectImpl
}
func (c *CacheSecondary) Name() string {
    return "cache-secondary"
}

// 或者使用版本区分
type CacheV1 struct {
    ioc.ObjectImpl
}
func (c *CacheV1) Version() string {
    return "1.0.0"
}

type CacheV2 struct {
    ioc.ObjectImpl
}
func (c *CacheV2) Version() string {
    return "2.0.0"
}

Q9: Close()方法什么时候被调用?

A: 当应用关闭时自动调用:

func main() {
    // server.Run会在收到信号时自动调用所有对象的Close()
    err := server.Run(context.Background())
}

手动调用:

// 手动触发关闭
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

ioc.CloseAll(ctx)  // 关闭所有命名空间的对象

Q10: 如何调试IOC容器问题?

A: 使用以下方法:

  1. 查看对象列表
fmt.Println("Registered objects:")
ioc.Controller().ForEach(func(obj *ioc.ObjectWrapper) {
    fmt.Printf("  - %s (v%s)\n", obj.Name, obj.Version)
})
  1. 检查依赖关系
// 参考 DEPENDENCY_VISUALIZATION.md 生成依赖图
  1. 启用调试日志
// 在ioc包中有debug日志输出
// 可以通过日志查看初始化过程
  1. 检查初始化错误
if err := ioc.InitAll(); err != nil {
    log.Printf("Init failed: %v", err)
    // 查看具体是哪个对象初始化失败
}

Q11: 性能考虑

Q: IOC容器对性能有影响吗?

A:

  • 初始化阶段:使用反射有一定开销,但只在启动时执行一次
  • 运行时:依赖已注入完成,无额外开销
  • 优化建议
    • 缓存Get()返回的对象引用,避免重复查找
    • 合理使用Priority()减少不必要的依赖等待
    • 避免在Init()中执行耗时操作,考虑延迟初始化

Q12: 与其他框架的集成

Q: 如何与Gin、GORM等框架集成?

A: mcube/ioc已内置集成:

import (
    _ "github.com/infraboard/mcube/v2/ioc/config/gin"      // Gin集成
    _ "github.com/infraboard/mcube/v2/ioc/config/datasource" // GORM集成
    _ "github.com/infraboard/mcube/v2/ioc/config/log"       // 日志集成
)

func main() {
    // 自动配置Gin、GORM等
    server.Run(context.Background())
}
目录