带有注解的定义
Koin Annotations 允许使用注解声明与常规 Koin DSL 相同类型的定义。只需使用所需的注解标记您的类,它就会为您生成一切!
例如,与 single { MyComponent(get()) } DSL 声明等效的操作只需使用 @Single 进行标记,如下所示:
@Single
class MyComponent(val myDependency : MyDependency)Koin Annotations 保持与 Koin DSL 相同的语义。您可以使用以下定义来声明组件:
@Single- 单例实例(在 DSL 中使用single { }声明)@Factory- factory 实例。例如,每次需要实例时都会重新创建。(在 DSL 中使用factory { }声明)@KoinViewModel- Android ViewModel 实例(在 DSL 中使用viewModel { }声明)@KoinWorker- Android Worker Workmanager 实例(在 DSL 中使用worker { }声明)
关于作用域,请查看 声明作用域 部分。
顶层函数注解
注解不仅适用于类,还适用于 顶层函数。这对于提供来自外部库或构建器模式的实例非常有用。与类一样,顶层函数也会被 @ComponentScan 发现:
import org.koin.core.annotation.Singleton
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Named
// 提供 Room 数据库实例
@Singleton
fun provideDatabase(context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "my-db").build()
// 提供 JSON 序列化器
@Singleton
fun provideJson(): Json = Json { ignoreUnknownKeys = true }
// 提供 HTTP 客户端
@Singleton
@Named("api")
fun provideHttpClient(json: Json): HttpClient = HttpClient { install(ContentNegotiation) { json(json) } }形参会从 DI 容器中自动解析。限定符(@Named,自定义 @Qualifier)既可以作用于函数,也可以作用于其形参。
模块函数(提供者函数)
在 @Module 类中,带有 @Singleton、@Factory 等注解的函数充当提供者函数 —— 类似于 Dagger/Hilt 中的 @Provides:
import org.koin.core.annotation.Module
import org.koin.core.annotation.Singleton
@Module
internal object DatabaseModule {
@Singleton
fun providesDatabase(context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "my-db").build()
}
@Module(includes = [DatabaseModule::class])
class DaosModule {
@Singleton
fun providesTopicDao(database: AppDatabase): TopicDao = database.topicDao()
@Singleton
fun providesNewsDao(database: AppDatabase): NewsResourceDao = database.newsResourceDao()
}这是包装您不拥有且无法直接添加注解的外部库(如 Room、Retrofit、OkHttp 等)的常用模式。
自定义限定符注解
除了 @Named,您还可以使用 @Qualifier 创建带有参数的自定义限定符注解:
import org.koin.core.annotation.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Dispatcher(val niaDispatcher: NiaDispatchers)
enum class NiaDispatchers { Default, IO }在提供者函数和注入点上使用它:
import org.koin.core.annotation.Module
import org.koin.core.annotation.Configuration
import org.koin.core.annotation.Singleton
@Module
@Configuration
class DispatchersModule {
@Singleton
@Dispatcher(NiaDispatchers.IO)
fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
@Singleton
@Dispatcher(NiaDispatchers.Default)
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@Singleton
fun providesApplicationScope(
@Dispatcher(NiaDispatchers.Default) dispatcher: CoroutineDispatcher,
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
}自定义限定符在编译时进行验证 —— 提供者与注入点之间的限定符不匹配将产生构建错误。
用于 Kotlin Multiplatform 的 ViewModel
@KoinViewModel 注解使用统一的 koin-core-viewmodel API 生成 ViewModel,从而提供 Kotlin Multiplatform 兼容性。
@KoinViewModel
class UserViewModel(val repository: UserRepository) : ViewModel()这将生成与 Android 和 Compose Multiplatform 兼容的 viewModel 定义。
自动或特定绑定
声明组件时,所有检测到的“绑定”(关联的超类型)都将为您准备就绪。例如,对于以下定义:
@Single
class MyComponent(val myDependency : MyDependency) : MyInterfaceKoin 会声明您的 MyComponent 组件也绑定到 MyInterface。等效的 DSL 为 single { MyComponent(get()) } bind MyInterface::class。
除了让 Koin 为您自动检测外,您还可以通过 binds 注解参数指定真正想要绑定的类型:
@Single(binds = [MyBoundType::class])可为 null 的依赖项
如果您的组件使用可为 null 的依赖项,请不用担心,它将为您自动处理。继续使用您的定义注解,Koin 会推断如何处理:
@Single
class MyComponent(val myDependency : MyDependency?)生成的等效 DSL 将为 single { MyComponent(getOrNull()) }
请注意,这也适用于注入的参数和属性
带有 @Named 的限定符
您可以为定义添加“名称”(也称为限定符),以使用 @Named 注解区分相同类型的多个定义:
@Single
@Named("InMemoryLogger")
class LoggerInMemoryDataSource : LoggerDataSource
@Single
@Named("DatabaseLogger")
class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSource在解析依赖项时,只需通过 named 函数使用该限定符:
val logger: LoggerDataSource by inject(named("InMemoryLogger"))也可以创建自定义限定符注解。沿用之前的示例:
@Named
annotation class InMemoryLogger
@Named
annotation class DatabaseLogger
@Single
@InMemoryLogger
class LoggerInMemoryDataSource : LoggerDataSource
@Single
@DatabaseLogger
class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSourceval logger: LoggerDataSource by inject(named<InMemoryLogger>())带有 @InjectedParam 的注入参数
您可以将构造函数成员标记为“注入参数”,这意味着在调用解析时,该依赖项将传递到图中。
例如:
@Single
class MyComponent(@InjectedParam val myDependency : MyDependency)然后您可以调用 MyComponent 并传入 MyDependency 的实例:
val m = MyDependency()
// 解析 MyComponent,同时传入 MyDependency
koin.get<MyComponent> { parametersOf(m) }生成的等效 DSL 将为 single { params -> MyComponent(params.get()) }
注入 Lazy 依赖项 - Lazy<T>
Koin 可以自动检测并解析 Lazy 依赖项。例如,在这里我们想要延迟解析 LoggerDataSource 定义。您只需按照如下方式使用 Lazy Kotlin 类型:
@Single
class LoggerInMemoryDataSource : LoggerDataSource
@Single
class LoggerAggregator(val lazyLogger : Lazy<LoggerDataSource>)在底层,它将生成类似于使用 inject() 而非 get() 的 DSL:
single { LoggerAggregator(inject()) }注入依赖项列表 - List<T>
Koin 可以自动检测并解析依赖项列表。例如,在这里我们想要解析所有的 LoggerDataSource 定义。您只需按照如下方式使用 List Kotlin 类型:
@Single
@Named("InMemoryLogger")
class LoggerInMemoryDataSource : LoggerDataSource
@Single
@Named("DatabaseLogger")
class LoggerLocalDataSource(private val logDao: LogDao) : LoggerDataSource
@Single
class LoggerAggregator(val datasource : List<LoggerDataSource>)在底层,它将生成类似于使用 getAll() 函数的 DSL:
single { LoggerAggregator(getAll()) }带有 @Property 的属性
要在定义中解析 Koin 属性,只需使用 @Property 标记构造函数成员。这将通过传递给注解的值来解析 Koin 属性:
@Factory
public class ComponentWithProps(
@Property("id") public val id : String
)生成的等效 DSL 将为 factory { ComponentWithProps(getProperty("id")) }
@PropertyValue - 带有默认值的属性(自 1.4 起)
Koin Annotations 让您能够直接在代码中使用 @PropertyValue 注解为属性定义默认值。 让我们参考示例:
@Factory
public class ComponentWithProps(
@Property("id") public val id : String
){
public companion object {
@PropertyValue("id")
public const val DEFAULT_ID : String = "_empty_id"
}
}生成的等效 DSL 将为 factory { ComponentWithProps(getProperty("id", ComponentWithProps.DEFAULT_ID)) }
JSR-330 兼容性注解
Koin Annotations 通过 koin-jsr330 模块提供与 JSR-330 (Jakarta Inject) 兼容的注解。这些注解对于从 Hilt、Dagger 或 Guice 等其他兼容 JSR-330 的框架迁移的开发者特别有用。
设置
将 koin-jsr330 依赖项添加到您的项目中:
dependencies {
implementation "io.insert-koin:koin-jsr330:$koin_version"
}可用的 JSR-330 注解
@Singleton (jakarta.inject.Singleton)
JSR-330 标准单例注解,等效于 Koin 的 @Single:
import jakarta.inject.Singleton
@Singleton
class DatabaseService这将生成与 @Single 相同的结果 —— Koin 中的单例实例。
@Named (jakarta.inject.Named)
用于基于字符串的限定符的 JSR-330 标准限定符注解:
import jakarta.inject.Named
import jakarta.inject.Singleton
@Singleton
@Named("inMemory")
class InMemoryCache : Cache
@Singleton
@Named("redis")
class RedisCache : Cache@Inject (jakarta.inject.Inject)
JSR-330 标准注入注解。虽然 Koin Annotations 不要求显式标记构造函数,但为了实现 JSR-330 兼容性,可以使用 @Inject:
import jakarta.inject.Inject
import jakarta.inject.Singleton
@Singleton
class UserService @Inject constructor(
private val repository: UserRepository
)@Qualifier (jakarta.inject.Qualifier)
用于创建自定义限定符注解的元注解:
import jakarta.inject.Qualifier
@Qualifier
annotation class Database
@Qualifier
annotation class Cache
@Singleton
@Database
class DatabaseConfig
@Singleton
@Cache
class CacheConfig@Scope (jakarta.inject.Scope)
用于创建自定义作用域注解的元注解:
import jakarta.inject.Scope
@Scope
annotation class RequestScoped
// 与 Koin 的作用域系统配合使用
@Scope(name = "request")
@RequestScoped
class RequestProcessor混合使用
您可以在同一个项目中自由混合使用 JSR-330 注解和 Koin 注解:
// JSR-330 风格
@Singleton
@Named("primary")
class PrimaryDatabase : Database
// Koin 风格
@Single
@Named("secondary")
class SecondaryDatabase : Database
// 在同一个类中混合使用
@Factory
class DatabaseManager @Inject constructor(
@Named("primary") private val primary: Database,
@Named("secondary") private val secondary: Database
)框架迁移优势
使用 JSR-330 注解为框架迁移提供了多项优势:
- 熟悉的 API:来自 Hilt、Dagger 或 Guice 的开发者可以使用已知的注解
- 渐进式迁移:现有的 JSR-330 注解代码只需极少改动即可工作
- 标准合规性:遵循 JSR-330 可确保与依赖注入标准兼容
- 团队入门:熟悉其他 DI 框架的团队更容易上手
INFO
Koin 中的 JSR-330 注解生成的底层 DSL 与其对应的 Koin 注解相同。选择 JSR-330 还是 Koin 注解纯粹是风格上的,取决于团队偏好或迁移需求。
