依赖注入
依赖注入(Dependency Injection, DI)是一种重要的软件设计模式,也是控制反转(Inversion of Control, IoC)原则的一种实现方式。它通过将对象的创建和绑定过程外部化,有效管理组件间的依赖关系,提高代码的可测试性和可维护性。
快速启动
只需要以下 5 个步骤即可开始使用依赖注入框架:
import spire_extensions_injection.*
class DbContext {
public init(connection: IDbConnection) {
}
}
interface IDbConnection {}
class MysqlConnection <: IDbConnection {}
// 1. 创建并配置服务集合
let services = ServiceCollection()
// 2. 注册服务描述
services.addScoped<DbContext, DbContext>()
services.addScoped<IDbConnection, MysqlConnection>()
// 3. 构建服务提供者
let provider = services.build()
// 4. 创建作业域
try (scope = provider.createScope()) {
// 5. 解析服务
let context = scope.services.getOrThrow<DbContext>()
}服务注册
基础注册
适用场景:简单的注册需求
// 完整描述符方式
services.add(ServiceDescriptor.singleton<IDbConnection, MysqlConnection>())
// 简化泛型方式(推荐)
services.addSingleton<IDbConnection, MysqlConnection>()
// 类型信息方式
services.addSingleton(TypeInfo.of<IDbConnection>(), TypeInfo.of<MysqlConnection>())实例注册
适用于已有实例对象的注册:
// 构建一个日志工厂
let loggerFactory = LoggingBuilder()
.addConsole()
.build()
// 注册到容器
services.addSingleton(loggerFactory)工厂注册
适用于需要自定义创建逻辑的场景:
interface IDbConnection {}
class MysqlConnection <: IDbConnection { public init(connectionString: String) { } }
class DbContext { public DbContext(public let connection: IDbConnection) { } }
public class ConfigurationManager {
public var connectionString = "mysql://localhost:3306" }
main() {
let services = ServiceCollection()
services.addSingleton(ConfigurationManager())
// 使用函数方式创建
services.addSingleton<IDbConnection, MysqlConnection> { sp =>
let configuration = sp.getOrThrow<ConfigurationManager>() // 可以通过sp来解析容器中已有的服务
return MysqlConnection(configuration.connectionString)
}
let provider = services.build()
let context = provider.getOrThrow<DbContext>()
}性能提示
工厂模式可以避免反射调用,提升性能
ActivatorUtilities 示例:
interface IDbConnection {}
interface ILoggerFactory {}
// 我们不会将String注册到容器
class DbContext { public init(connection: IDbConnection, loggerFactory: ILoggerFactory, tenantId: String) { } }
class MysqlConnection <: IDbConnection { public init(connectionString: String) { } }
class LoggerFactory <: ILoggerFactory { public init() { } }
main() {
let services = ServiceCollection()
services.addSingleton<IDbConnection, MysqlConnection> { sp =>
MysqlConnection("mysql://localhost:3306/testdb")
}
services.addSingleton<ILoggerFactory, LoggerFactory>()
// 使用ActivatorUtilities
services.addSingleton<DbContext> { sp =>
// 支持传入自定义参数
ActivatorUtilities.createInstance<DbContext>(sp, ["t1"])
}
let provider = services.build()
let context = provider.getOrThrow<DbContext>()
}
ActivatorUtilities在服务注册和服务解析都存在使用需求
注意
ActivatorUtilities 会使用反射创建实例,如需完全避免反射应手动解析依赖
服务解析
依赖注入框架提供多种服务解析方式,并使用不同的内部机制来创建和管理服务实例。
基本解析方法
解析必需服务
确保服务已注册时使用:
let services = ServiceCollection()
services.addSingleton<IDbConnection, MysqlConnection>()
let provider = services.build()
// 如果服务不存在将抛出异常
let connection = provider.getOrThrow<IDbConnection>()解析可选服务
不确定服务是否注册时使用:
let services = ServiceCollection()
let provider = services.build()
// 如果服务解析失败返回None
let connection = provider.getOrDefault<IDbConnection>() ?? MysqlConnection()解析多实现服务
适用于策略模式或插件系统:
interface IDbConnection {}
class MysqlConnection <: IDbConnection {}
class SqlServerConnection <: IDbConnection {}
let services = ServiceCollection()
// 注册多个数据库连接实现
services.addSingleton<IDbConnection, MysqlConnection>()
services.addSingleton<IDbConnection, SqlServerConnection>()
let provider = services.build()
// 获取所有注册的连接实现
let connections = provider.getAll<IDbConnection>()通过容器创建服务
解析未注册但依赖容器的服务:
class DbContext {
public init(connection: IDbConnection, loggerFactory: ILoggerFactory, tenant: String) {
}
}
let services = ServiceCollection()
services.addSingleton<IDbConnection, MysqlConnection>()
services.addSingleton<ILoggerFactory, LoggerFactory>()
let provider = services.build()
// 使用ActivatorUtilities创建未注册服务
let context = ActivatorUtilities.createInstance<DbContext>(provider, ["tenantId"])集合解析
多个相同类型服务的批量解析:
interface IDbConnection {}
class MysqlConnection <: IDbConnection {}
class SqlServerConnection <: IDbConnection {}
class PostgreSqlConnection <: IDbConnection {}
class ConnectionManager { public init(connections: Collection<IDbConnection>) { } }
main(): Unit {
let services = ServiceCollection()
// 注册多个数据库连接
services.addSingleton<IDbConnection, MysqlConnection>()
services.addSingleton<IDbConnection, SqlServerConnection>()
services.addSingleton<IDbConnection, PostgreSqlConnection>()
// 注册连接管理器
services.addSingleton<ConnectionManager> { sp =>
let connections = sp.getAll<IDbConnection>()
return ConnectionManager(connections)
}
let provider = services.build()
provider.getAll<IDbConnection>()
provider.getOrThrow<ConnectionManager>()
}内建服务
依赖注入框架自动提供三个内建服务,这些服务在构建容器时会被自动注册,无需手动注册:
IServiceProvider
提供服务解析的核心接口,容器会自动注册自身作为 IServiceProvider 的实现。
let services = ServiceCollection()
services.addScoped<DbContext>()
let provider = services.build()
// 可以注入IServiceProvider来解析其他服务
services.addScoped<UserService> { sp =>
// sp就是IServiceProvider的实例
let dbContext = sp.getOrThrow<DbContext>()
return UserService(dbContext)
}IServiceScopeFactory
提供创建服务作用域的工厂接口,用于管理服务的生命周期。
let services = ServiceCollection()
services.addScoped<DbContext>()
let provider = services.build()
// 注入IServiceScopeFactory来创建新的作用域
services.addSingleton<BackgroundService> { sp =>
let scopeFactory = sp.getOrThrow<IServiceScopeFactory>()
return BackgroundService(scopeFactory)
}
class BackgroundService {
private let scopeFactory: IServiceScopeFactory
public init(scopeFactory: IServiceScopeFactory) {
this.scopeFactory = scopeFactory
}
public func processTask() {
// 在后台任务中创建新的作用域
try (scope = scopeFactory.createScope()) {
let dbContext = scope.services.getOrThrow<DbContext>()
// 处理业务逻辑
}
}
}IServiceProviderIsService
提供判断服务是否已注册的接口,用于运行时检查服务的可用性。
let services = ServiceCollection()
services.addScoped<DbContext>()
let provider = services.build()
// 注入IServiceProviderIsService来检查服务是否可用
services.addSingleton<FeatureManager> { sp =>
let serviceChecker = sp.getOrThrow<IServiceProviderIsService>()
return FeatureManager(serviceChecker)
}
class FeatureManager {
private let serviceChecker: IServiceProviderIsService
public init(serviceChecker: IServiceProviderIsService) {
this.serviceChecker = serviceChecker
}
public func isFeatureEnabled(serviceType: TypeInfo): Bool {
// 检查特定服务是否已注册来判断功能是否可用
return serviceChecker.isService(serviceType)
}
}自动注册
这三个内建服务在调用 services.build() 时会自动注册到容器中,它们的生命周期由框架管理,你无需关心。
生命周期
| 生命周期 | 作用范围 | 典型应用场景 |
|---|---|---|
| Singleton | 整个应用程序生命周期 | 配置服务、日志服务 |
| Scoped | 单个作用域范围内 | 数据库上下文 |
| Transient | 每次请求创建新实例 | 轻量级临时服务 |
重要规则
- 单例服务不能依赖非单例服务
- 不能从根容器解析非单例服务
作用域示例
interface IDbConnection {}
class MysqlConnection <: IDbConnection {}
main(): Unit {
let services = ServiceCollection()
// 注册为Singleton
services.addSingleton<IDbConnection, MysqlConnection>()
// 注册为Scoped
services.addScoped<IDbConnection, MysqlConnection>()
// 注册为Transient
services.addTransient<IDbConnection, MysqlConnection>()
let provider = services.build()
// 创建作用域
try(scope = provider.createScope()) {
// 解析服务
scope.services.getOrThrow<IDbConnection>()
}
// 程序运行到此处,如果IDbConnection注册方式是非单例的,就会被释放
}资源释放
如果服务实现了 Resource 接口,在释放时容器会调用实例的 close 方法,来释放 非托管资源。
高级功能
防止服务重复注册
重复注册问题:假设 addAuthorization 和 addMemoryCache 是第三方开发者开发的,调用方不知道内部的注册逻辑。会导致某些服务重复注册
// 定义扩展方法1:
extend ServiceCollection {
public func addMemoryCache() {
//使用add如果重复执行将导致服务注册多次
this.addSingleton<IMemoryCache, MemoryCache>()
}
}
// 定义扩展方法1:
extend ServiceCollection {
public func addAuthorization() {
this.addMemoryCache()
this.addSingleton<AuthorizationService>()
}
}
// 会导致IMemoryCache重复注册
let services = ServiceCollection()
services.addMemoryCache()
//*** 重复注册IMemoryCache
services.addAuthorization()尝试注册服务:如果多个方法内部多次注册了同一个服务,为了避免服务重复注册可以使用 tryAdd
// 定义扩展方法1:
extend ServiceCollection {
public func addMemoryCache() {
this.addSingleton<IMemoryCache, MemoryCache>()
this.tryAddSingleton<IMemoryCache, MemoryCache>()
}
}
// 定义扩展方法1:
extend ServiceCollection {
public func addAuthorization() {
this.addMemoryCache()
this.tryAddSingleton<AuthorizationService>()
}
}
let services = ServiceCollection()
//抢先注册:替换掉IMemoryCache的实现
services.addSingleton<IMemoryCache, MyMemoryCache>()
services.addMemoryCache()
services.addAuthorization()注册策略
- 如果一个服务使用
tryAdd的方式注册的,表示该服务只允许注册一次 - 如果已知某个服务使用
tryAdd的方式注册,那么可以通过在它注册之前抢先注册来替换掉默认实现
容器构建选项
let provider = services.build { options =>
options.validateScopes = true // 确保不会从根容器解析 Scoped 等
options.validateOnBuild = true // 构建时做一次依赖/生命周期验证
}说明:循环依赖在生成服务图时会自动检测并抛出异常,无需也不支持开关。
最佳实践
性能优化建议
- 高频服务优先使用工厂函数或实例注册避免反射并使用 Transient 生命周期
- 单例服务推荐使用实例注册获得最佳性能
- 复杂创建使用工厂函数而非构造器注入
- 多实现合理使用集合解析实现多数据、插件等源架构
生命周期选择
- Singleton: 无状态全局服务
- Scoped: 请求相关有状态服务
- Transient: 轻量级临时服务
线程安全
- ServiceCollection 构建前非线程安全
- IServiceProvider 完全线程安全
开发建议
- 开发环境启用所有验证选项
- 生产环境关闭非必要检查提升性能