文件最后提交记录最后更新时间
Add DI test for singleton instance ownership 1 天前
refactor!(injection): 收窄 ServiceDescriptor 构造函数可见性并规范化工厂签名 - ServiceDescriptor 三个 public init 改为 internal init,强制调用方使用静态工厂方法 (singleton/scoped/transient 及其 keyed 变体),避免直接构造导致的类型安全绕过 - 提取 requireSubtype(serviceType:implementationType:) 私有静态方法,消除三处重复的 子类型校验逻辑(DRY 原则) - ServiceDescriptor 工厂 init 新增 implementationType: ?TypeInfo 参数,允许在工厂注册时 同时记录实现类型,使 getImplementationType() 在工厂场景下也能返回正确类型 - ServiceCollectionExtensions 所有工厂重载(addSingleton/addScoped/addTransient 及 tryAdd/keyed 变体)工厂参数类型由 (IServiceProvider) -> Any 改为 (IServiceProvider) -> TService,提升类型安全性,消除调用方的 as Any 强转需求 - tryAddEnumerable(all:) 参数重命名为 descriptors,内部循环变量重命名为 descriptor, 提升代码可读性 - 单测函数名从中文改为英文(snake_case),与项目其他模块保持一致 - service_getter_tests.cj 重命名为 service_getter_test.cj,统一文件命名规范 - 新增 CHANGELOG.md,记录 v1.1.0 破坏性变更 BREAKING CHANGE: ServiceDescriptor 三个 public init 已改为 internal; 请改用 ServiceDescriptor.singleton/scoped/transient<TService>() 等静态工厂方法。 工厂重载参数类型由 (IServiceProvider) -> Any 改为 (IServiceProvider) -> TService, 工厂闭包返回值需与 TService 类型一致。 2 个月前
docs: 批量更新各模块 README.md,遵循 cj-readme 规范 更新 soulsoft_extensions_*、soulsoft_identity_*、soulsoft_net_http、 soulsoft_serialization、soulsoft_web_* 等模块的 README.md 文档 1 个月前
docs(extensions-injection): 更新模块描述文案 - 调整 cjpm.toml 中的 description 文案以统一模块说明口径 - 精简表述并突出当前模块的核心职责 - 保持包元数据与仓库整体命名风格一致 2 个月前
README.md

soulsoft.extensions.injection

依赖注入容器,提供服务注册、服务解析、作用域管理、带键服务与构造函数注入。

关于

soulsoft.extensions.injection 提供一个轻量 DI 容器。核心类型包括 ServiceCollection(注册服务)、IServiceProvider(解析服务)和 IServiceScope(管理作用域)。库支持 SingletonScopedTransient 三种生命周期,支持带键服务注册与解析,支持通过 @FromKeyedServices@ServiceKey 进行构造函数参数注入,也支持 ActivatorUtilities 创建未注册类型实例。

ServiceCollection.build() 通过 Mutex 保护构建过程,ServiceProvider 使用 ConcurrentHashMap 缓存解析入口,并用原子状态管理关闭流程。

特性

特性 描述
🔄 三种生命周期 支持 SingletonScopedTransient 三种注册与解析语义
🔑 带键服务 同一服务类型可通过不同 serviceKey 注册多个实现,并按键解析
📦 多实现解析 getAll<T>() / getAll<T>(serviceKey) 返回同类型全部实现,顺序与注册顺序一致
🏗️ 工厂注册 支持 (IServiceProvider) -> T 工厂,工厂内部可继续解析依赖
🧩 构造函数注入 自动按构造函数参数解析服务,支持 ActivatorUtilities 为未注册类型补齐依赖
🏷️ 参数注解注入 @FromKeyedServices 支持显式 key 或继承 key,@ServiceKey 可把注册键注入为 String
♻️ 资源释放 IServiceScope 继承 Resource,作用域结束时自动释放该作用域内捕获的资源
构建期校验 validateOnBuild 可在 build() 时验证服务图;多个失败会聚合为 AggregateException
🔒 作用域校验 validateScopes 可阻止从根容器错误解析 Scoped 服务

如何使用

基本注册与解析

import soulsoft_extensions_injection.*

interface IRepository {}
class Repository <: IRepository {}
class AppService {
    public AppService(public let repo: IRepository) {}
}

let services = ServiceCollection()
services.addSingleton<IRepository, Repository>()
services.addTransient<AppService, AppService>()

let root = services.build()

let repo = root.getOrThrow<IRepository>()
let allRepos = root.getAll<IRepository>()
let app = root.getOrThrow<AppService>()

作用域与校验

import soulsoft_extensions_injection.*

interface IDbConnection <: Resource {}
class MySqlConnection <: IDbConnection {
    public init() {}
    public func close(): Unit {}
}

let services = ServiceCollection()
services.addScoped<IDbConnection, MySqlConnection>()

let root = services.build({ options =>
    options.validateScopes = true
    options.validateOnBuild = true
})

try (scope = root.createScope()) {
    let connection = scope.services.getOrThrow<IDbConnection>()
}

带键服务与 ActivatorUtilities

import soulsoft_extensions_injection.*

interface IDbConnection {}
class MySqlConnection <: IDbConnection {}
class MsSqlConnection <: IDbConnection {}

class Repository {
    public Repository(
        @FromKeyedServices["mysql"] public let connection: IDbConnection,
        @ServiceKey public let key: String
    ) {}
}

class ReportService {
    public ReportService(public let repo: Repository, public let name: String) {}
}

let services = ServiceCollection()
services.addKeyedSingleton<IDbConnection, MySqlConnection>("mysql")
services.addKeyedSingleton<IDbConnection, MsSqlConnection>("mssql")
services.addKeyedSingleton<Repository, Repository>("mysql")

let root = services.build()
let repo = root.getOrThrow<Repository>("mysql")
let report = ActivatorUtilities.createInstance<ReportService>(root, ["monthly"])

API 参考

IServiceProvider

方法 说明
get(serviceType: TypeInfo): ?Any 按运行时类型解析服务,未注册返回 None
get(serviceKey: String, serviceType: TypeInfo): ?Any 按 key 和运行时类型解析服务
get<T>(): ?T 按泛型类型解析服务,未注册返回 None
get<T>(serviceKey: String): ?T 按 key 解析带键服务
getOrThrow(serviceType: TypeInfo): Any 按运行时类型解析,失败抛 UnsupportedException
getOrThrow<T>(): T 按泛型类型解析,失败抛 UnsupportedException
getOrThrow<T>(serviceKey: String): T 按 key 解析,失败抛 UnsupportedException
getAll<T>(): Collection<T> 返回该类型全部实现,无注册时返回空集合
getAll<T>(serviceKey: String): Collection<T> 返回指定 key 下该类型全部实现
createScope(): IServiceScope 创建新作用域

IServiceScope

成员 说明
prop services: IServiceProvider 当前作用域对应的服务提供者

IServiceScope 继承 Resource,可直接用于 try-with-resource

ServiceCollection 与扩展

方法 说明
addSingleton<TService>(instance: TService) 注册单例实例
addSingleton<TService, TImplementation>() 注册类型映射单例
addSingleton<TService>(factory: (IServiceProvider) -> TService) 注册单例工厂
addScoped<TService, TImplementation>() 注册作用域服务
addScoped<TService>(factory: (IServiceProvider) -> TService) 注册作用域工厂
addTransient<TService, TImplementation>() 注册瞬时服务
addTransient<TService>(factory: (IServiceProvider) -> TService) 注册瞬时工厂
addKeyedSingleton / addKeyedScoped / addKeyedTransient 注册带键服务,对应三种生命周期均提供实例、类型映射、工厂与 TypeInfo 重载
tryAdd* 仅当相同 serviceType + serviceKey 未注册时添加
tryAddEnumerable(descriptor: ServiceDescriptor) 仅当相同 serviceType + implementationType + serviceKey 未注册时添加
build(): IServiceProvider 使用默认选项构建容器
build(configureOptions: (ServiceProviderOptions) -> Unit): IServiceProvider 构建时配置校验选项

ServiceProviderOptions

属性 类型 默认值 说明
validateScopes Bool false 开启后阻止从根容器直接解析 Scoped 服务
validateOnBuild Bool false 开启后在构建阶段验证服务图;多个失败聚合为 AggregateException

ActivatorUtilities

方法 说明
createInstance<T>(provider: IServiceProvider, parameters: Array<Any>): T 创建未注册类型实例,用户参数优先匹配,剩余参数从容器解析
createInstance(provider: IServiceProvider, serviceType: TypeInfo, parameters: Array<Any>): Object 动态版本,不能创建抽象类型

其他公开类型

类型 说明
ServiceDescriptor 单条服务注册描述,公开暴露 serviceTypeserviceKeyimplementationTypelifetime 等属性
ServiceLifetime 生命周期枚举:SingletonScopedTransient
IServiceProviderIsService isService(serviceType: TypeInfo): Bool,检测某类型是否可解析
IServiceProviderIsKeyedService isKeyedService(serviceKey: String, serviceType: TypeInfo): Bool,检测某个 key 下的类型是否可解析
FromKeyedServices 参数注解;@FromKeyedServices["mysql"] 显式指定 key,@FromKeyedServices 继承父服务 key
ServiceKey 参数注解;将当前服务注册 key 注入到 String 参数
AggregateException validateOnBuild 多个服务校验失败时抛出的聚合异常

相关包

当前模块 cjpm.toml 未声明额外依赖包。


开源声明

本库的核心实现参考了 dotnet/runtime 中的依赖注入模块,原始代码版权归 .NET Foundation and Contributors 所有,遵循 MIT 许可证。详见 README.OpenSource


反馈与贡献

本项目由杭州颉创科技有限公司开发,以 MIT 许可证开源发布。欢迎在 GitCode 提交错误报告和贡献代码。