依赖注入
依赖注入(Dependency Injection, DI)是一种重要的软件设计模式,也是控制反转(Inversion of Control, IoC)原则的一种实现方式。它通过将对象的创建和绑定过程外部化,有效管理组件间的依赖关系,提高代码的可测试性和可维护性。
快速启动
只需要以下 5 个步骤即可开始使用依赖注入框架:
cangjie
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>()
}
服务注册
基础注册
适用场景:简单的注册需求
cangjie
// 完整描述符方式
services.add(ServiceDescriptor.singleton<IDbConnection, MysqlConnection>())
// 简化泛型方式(推荐)
services.addSingleton<IDbConnection, MysqlConnection>()
// 类型信息方式
services.addSingleton(TypeInfo.of<IDbConnection>(), TypeInfo.of<MysqlConnection>())
实例注册
适用于已有实例对象的注册:
cangjie
// 构建一个日志工厂
let loggerFactory = LoggingBuilder()
.addConsole()
.build()
// 注册到容器
services.addSingleton(loggerFactory)
工厂注册
适用于需要自定义创建逻辑的场景:
cangjie
public class ConfigurationManager {
public var connectionString = "mysql://localhost:3306"
}
let services = ServiceCollection()
services.addSingleton(ConfigurationManager())
// 使用函数方式创建
services.addSingleton<IDbConnection> { sp =>
let configuration = sp.getOrThrow<ConfigurationManager>() // 可以通过sp来解析容器中已有的服务
return MysqlConnection(configuration.connectionString)
}
let provider = services.build()
let context = provider.getOrThrow<DbContext>()
性能提示
工厂模式可以避免反射调用,提升性能
ActivatorUtilities 示例:
cangjie
// 我们不会将String注册到容器
class DbContext {
public init(connection: IDbConnection, loggerFactory: ILoggerFactory, tenantId: String) {
}
}
let services = ServiceCollection()
services.addSingleton<IDbConnection, MysqlConnection>()
services.addSingleton<ILoggerFactory, LoggerFactory>()
//使用ActivatorUtilities
services.addSingleton<DbContext> { sp =>
//支持传入自定义参数
ActivatorUtilities.createInstance<DbContext>(sp, ["t1"])
}
let provider = services.build()
let context = provider.getOrThrow<DbContext>()
注意
ActivatorUtilities
会使用反射创建实例,如需完全避免反射应手动解析依赖
服务解析
解析必需服务
确保服务已注册时使用:
cangjie
let services = ServiceCollection()
services.addSingleton<IDbConnection, MysqlConnection>()
let provider = services.build()
// 如果服务不存在将抛出异常
let connection = provider.getOrThrow<IDbConnection>()
解析可选服务
不确定服务是否注册时使用:
cangjie
let services = ServiceCollection()
let provider = services.build()
//如果服务解析失败返回None
let connection = provider.getOrDefault<IDbConnection>() ?? MysqlConnection()
解析多实现服务
适用于策略模式或插件系统:
cangjie
let services = ServiceCollection()
// 注册多个数据源
services.addSingleton<IDataSource, MysqlDataSource>()
services.addSingleton<IDataSource, SqlServerDataSource>()
//构建容器
let provider = services.build()
// 获取所有数据源
let strategies = provider.getAll<IDataSource>()
通过容器创建服务
解析未注册但依赖容器的服务:
cangjie
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()
//假设DbContext不希望注册到容器中
let context = ActivatorUtilities.createInstance<DbContext>(provider, ["t1"])
ActivatorUtilities
在服务注册和服务解析都存在使用需求
生命周期
生命周期 | 作用范围 | 典型应用场景 |
---|---|---|
Singleton | 整个应用程序生命周期 | 配置服务、日志服务 |
Scoped | 单个作用域范围内 | 数据库上下文 |
Transient | 每次请求创建新实例 | 轻量级临时服务 |
重要规则
- 单例服务不能依赖非单例服务
- 不能从根容器解析非单例服务
作用域示例
cangjie
let services = ServiceCollection()
// 注册为Singleton
services.addSingleton<IDbConnection, MysqlConnection>()
// 注册为Scoped
services.addScoped<IDbConnection, MysqlConnection>()
// 注册为Transient
services.addScoped<IDbConnection, MysqlConnection>()
let provider = services.build()
// 创建作用域
try(scope = provider.createScope()) {
// 解析服务
scope.services.getOrThrow<IDbConnection>()
}
// 程序运行到此处,如果IDbConnection注册方式是非单例的,就会被释放
资源释放
如果服务实现了 Resource
接口,在释放时容器会调用实例的 close
方法,来释放 非托管资源
。
高级功能
防止服务重复注册
重复注册问题:假设 addAuthorization
和 addMemoryCache
是第三方开发者开发的,调用方不知道内部的注册逻辑。会导致某些服务重复注册
cangjie
//定义扩展方法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
cangjie
//定义扩展方法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
的方式注册,那么可以通过在它注册之前抢先注册来替换掉默认实现
容器构建选项
cangjie
let provider = services.build { options =>
options.validateScopes = true // 可以确保没有作用域服务被错误地从根提供程序解析
options.validateLoopDependency = true // 启用循环依赖检测
}
最佳实践
性能优化
- 优先使用工厂注册替代反射
- 高频创建的服务考虑使用 Transient 生命周期
生命周期选择
- Singleton: 无状态全局服务
- Scoped: 请求相关有状态服务
- Transient: 轻量级临时服务
线程安全
- ServiceCollection 构建前非线程安全
- IServiceProvider 完全线程安全
开发建议
- 开发环境启用所有验证选项
- 生产环境关闭非必要检查提升性能