依赖注入
ACE Framework 的依赖注入(DI)基于编译期宏实现,无任何运行时反射。框架在编译阶段将 @Service、@Controller 等宏展开为顶层常量声明,程序启动时这些初始化器自动执行,完成 Bean 注册——与 Spring 的运行时扫描路线截然不同。
IoC 容器工作原理
当你在类上标注 @Service 时,宏会在同一文件的顶层生成一条注册语句:
// 你写的代码
@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 时自动填充依赖:
@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"] 在注入点指定具体实现:
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 作用域
| 宏 | 作用域 | 说明 |
|---|---|---|
@Service | Singleton | 全局唯一实例,首次 resolve 后缓存 |
@Prototype | Prototype | 每次 resolve 创建新实例 |
@Service["request"] | Request | 每个 HTTP 请求一个实例,请求结束自动销毁 |
@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 手动获取:
import ace.framework.runtime.*
func main() {
let app = AceApplication.runWithArgs(getArgs())
let svc = resolveBean<UserService>() // 泛型推断类型
svc.init()
}BeanPostProcessor
通过 registerContainerHook 在每个 Bean resolve 后执行后处理逻辑,适合统一注入链路追踪、AOP 代理等:
import ace.framework.runtime.*
registerContainerHook { beanName, instance in
if instance is Traceable {
(instance as Traceable).setTracer(globalTracer)
}
instance
}条件装配
@Conditional
根据配置值决定是否注册 Bean:
@Service
@Conditional["feature.cache=true"]
class RedisCache <: CacheProvider {
// 仅当 ace.yaml 中 feature.cache=true 时注册
}@Profile
根据当前运行环境(--env 参数)决定是否装配:
@Service
@Profile["prod"]
class ProdMailSender <: MailSender { ... }
@Service
@Profile["dev"]
class MockMailSender <: MailSender { ... }# 以 prod 环境启动
./my-app --env prod@Conditional 键值对格式
@Conditional["key=value"] 中 = 两侧不能有空格,值区分大小写。配置键采用点分隔路径,与 ace.yaml 键路径一致。
完整示例:接口 + 多实现 + @Qualifier
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)
}
}