Skip to content

依赖注入

依赖注入(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
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 示例:

cangjie
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 会使用反射创建实例,如需完全避免反射应手动解析依赖

服务解析

依赖注入框架提供多种服务解析方式,并使用不同的内部机制来创建和管理服务实例。

基本解析方法

解析必需服务

确保服务已注册时使用:

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
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>()

通过容器创建服务

解析未注册但依赖容器的服务:

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()

// 使用ActivatorUtilities创建未注册服务
let context = ActivatorUtilities.createInstance<DbContext>(provider, ["tenantId"])

集合解析

多个相同类型服务的批量解析:

cangjie
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 的实现。

cangjie
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

提供创建服务作用域的工厂接口,用于管理服务的生命周期。

cangjie
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

提供判断服务是否已注册的接口,用于运行时检查服务的可用性。

cangjie
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每次请求创建新实例轻量级临时服务

重要规则

  1. 单例服务不能依赖非单例服务
  2. 不能从根容器解析非单例服务

作用域示例

cangjie
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 方法,来释放 非托管资源

高级功能

防止服务重复注册

重复注册问题:假设 addAuthorizationaddMemoryCache 是第三方开发者开发的,调用方不知道内部的注册逻辑。会导致某些服务重复注册

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()

注册策略

  1. 如果一个服务使用 tryAdd 的方式注册的,表示该服务只允许注册一次
  2. 如果已知某个服务使用 tryAdd 的方式注册,那么可以通过在它注册之前抢先注册来替换掉默认实现

容器构建选项

cangjie
let provider = services.build { options =>
    options.validateScopes = true   // 确保不会从根容器解析 Scoped 等
    options.validateOnBuild = true  // 构建时做一次依赖/生命周期验证
}

说明:循环依赖在生成服务图时会自动检测并抛出异常,无需也不支持开关。

最佳实践

性能优化建议

  • 高频服务优先使用工厂函数或实例注册避免反射并使用 Transient 生命周期
  • 单例服务推荐使用实例注册获得最佳性能
  • 复杂创建使用工厂函数而非构造器注入
  • 多实现合理使用集合解析实现多数据、插件等源架构

生命周期选择

  • Singleton: 无状态全局服务
  • Scoped: 请求相关有状态服务
  • Transient: 轻量级临时服务

线程安全

  • ServiceCollection 构建前非线程安全
  • IServiceProvider 完全线程安全

开发建议

  • 开发环境启用所有验证选项
  • 生产环境关闭非必要检查提升性能