Skip to content

依赖注入

ACE Framework 的依赖注入(DI)基于编译期宏实现,无任何运行时反射。框架在编译阶段将 @Service@Controller 等宏展开为顶层常量声明,程序启动时这些初始化器自动执行,完成 Bean 注册——与 Spring 的运行时扫描路线截然不同。

IoC 容器工作原理

当你在类上标注 @Service 时,宏会在同一文件的顶层生成一条注册语句:

cangjie
// 你写的代码
@Service
class UserService {
    func findUser(id: Int64): User { ... }
}

// 宏展开后(等价代码,可用 --debug-macro 查看)
let __ace_reg_UserService = Registry.register("UserService", { -> UserService() })

注册表 Registry 是全局单例,所有 __ace_reg_* 常量的初始化器在进入 main() 之前由运行时依次执行,确保 Bean 在首次使用前已就绪。

零反射

整个注册流程不依赖类型反射。生成的代码是普通函数调用,AOT 编译后与手写注册代码性能完全一致。

字段注入 @Inject

在类的字段上添加 @Inject,容器将在首次 resolve 该 Bean 时自动填充依赖:

cangjie
@Service
class OrderService {
    @Inject
    var userService!: UserService   // 类型即查找键,无需指定名称

    @Inject
    var db!: Database

    func createOrder(userId: Int64): Order {
        let user = userService.findUser(userId)
        // ...
    }
}

字段必须可变

被注入字段声明为 var,并使用 ! 后缀(延迟初始化)。容器会在 resolve 时赋值,运行期不会再变更。

接口注入:@Primary 与 @Qualifier

当一个接口有多个实现时,使用 @Primary 标记默认实现,用 @Qualifier["name"] 在注入点指定具体实现:

cangjie
interface PaymentGateway {
    func charge(amount: Float64): Bool
}

@Service
@Primary
class AlipayGateway <: PaymentGateway {
    func charge(amount: Float64): Bool { ... }
}

@Service
class WechatGateway <: PaymentGateway {
    func charge(amount: Float64): Bool { ... }
}

@Service
class CheckoutService {
    @Inject
    var defaultGateway!: PaymentGateway          // 注入 AlipayGateway(@Primary)

    @Inject
    @Qualifier["WechatGateway"]
    var wechatGateway!: PaymentGateway           // 显式指定微信
}

Bean 作用域

作用域说明
@ServiceSingleton全局唯一实例,首次 resolve 后缓存
@PrototypePrototype每次 resolve 创建新实例
@Service["request"]Request每个 HTTP 请求一个实例,请求结束自动销毁
cangjie
@Prototype
class IdGenerator {
    let id: Int64 = generateUniqueId()
}

@Service["request"]
class RequestContext {
    var traceId: String = ""
}

Request 作用域

Request 作用域 Bean 只能注入到其他 Request 作用域或 Prototype Bean 中,不可注入到 Singleton,否则启动期抛出循环作用域错误。

循环依赖检测

容器在启动期(首次 resolve 时)会检测循环引用,发现时立即抛出异常并打印完整引用链:

DependencyCycleError: circular dependency detected
  OrderService -> UserService -> OrderService

解决方式:将其中一个依赖改为 Prototype,或引入第三个协调类解耦。

手动获取 Bean

在没有注入上下文的场合(如工具函数、main 入口),可通过 resolveBean 手动获取:

cangjie
import ace.framework.runtime.*

func main() {
    let app = AceApplication.runWithArgs(getArgs())
    let svc = resolveBean<UserService>()  // 泛型推断类型
    svc.init()
}

BeanPostProcessor

通过 registerContainerHook 在每个 Bean resolve 后执行后处理逻辑,适合统一注入链路追踪、AOP 代理等:

cangjie
import ace.framework.runtime.*

registerContainerHook { beanName, instance in
    if instance is Traceable {
        (instance as Traceable).setTracer(globalTracer)
    }
    instance
}

条件装配

@Conditional

根据配置值决定是否注册 Bean:

cangjie
@Service
@Conditional["feature.cache=true"]
class RedisCache <: CacheProvider {
    // 仅当 ace.yaml 中 feature.cache=true 时注册
}

@Profile

根据当前运行环境(--env 参数)决定是否装配:

cangjie
@Service
@Profile["prod"]
class ProdMailSender <: MailSender { ... }

@Service
@Profile["dev"]
class MockMailSender <: MailSender { ... }
bash
# 以 prod 环境启动
./my-app --env prod

@Conditional 键值对格式

@Conditional["key=value"]= 两侧不能有空格,值区分大小写。配置键采用点分隔路径,与 ace.yaml 键路径一致。

完整示例:接口 + 多实现 + @Qualifier

cangjie
package com.example.service

import ace.framework.*

interface NotificationSender {
    func send(to: String, msg: String): Unit
}

@Service
@Primary
class EmailSender <: NotificationSender {
    func send(to: String, msg: String): Unit {
        println("Email -> ${to}: ${msg}")
    }
}

@Service
class SmsSender <: NotificationSender {
    func send(to: String, msg: String): Unit {
        println("SMS -> ${to}: ${msg}")
    }
}

@Service
class AlertService {
    @Inject
    var email!: NotificationSender           // 使用 @Primary → EmailSender

    @Inject
    @Qualifier["SmsSender"]
    var sms!: NotificationSender             // 显式 SmsSender

    func alertAll(to: String, msg: String): Unit {
        email.send(to, msg)
        sms.send(to, msg)
    }
}

基于 Apache-2.0 许可证发布