前言
Koin 是一个用纯 Kotlin 编写的轻量级依赖注入(Dependency Injection, DI)框架,其设计目标是简单易用,不依赖代码生成或复杂的注解处理机制。
Github: github.com/InsertKoinI…
通过源码目录结构我们可以知道,Koin使用的是基于 Kotlin Multiplatform 开发的项目,所以能够实现多平台的依赖注入。
入门
需要添加依赖
dependencies {
implementation("io.insert-koin:koin-android:$koin_android_version")
}
示例代码如下:
class TestA(val type: String)
val factoryModule = module {
factory { TestA(System.currentTimeMillis().toString()) }
}
class KoinApp : Application() {
override fun onCreate() {
super.onCreate()
// 1. 初始化
startKoin {
androidContext(this@KoinApp)
modules(factoryModule)
}
// 2. 使用扩展函数 get(), 获取对应的实例。或是使用委托 https://book.kotlincn.net/text/delegated-properties.html
val koinTestA: TestA = get()
}
}
-
使用 startKoin 扩展方法初始化Koin
- androidContext: KoinApplication 的扩展方法,为了给内部的全局Context变量赋值,后续所有的module都可以通过get()方法获取这个context(Application)。
- modules:KoinApplication 中的方法,实际上调用的是 Koin 对象中的 loadModules 方法,往Map中注册对应的数据。
-
使用扩展方法 get() 获取实例
通过上面两步,我们就能够通过Koin实现依赖注入。那么下面就详细的分析一下Koin的实现原理。
源码分析
startKoin
/**
* Start a Koin Application as StandAlone
*/
@KoinApplicationDslMarker
fun startKoin(koinApplication: KoinApplication): KoinApplication = KoinPlatformTools.defaultContext().startKoin(koinApplication)
/**
* Start a Koin Application as StandAlone
*/
// KoinAppDeclaration 是 KoinApplication 匿名拓展函数 KoinApplication.() -> Unit
@KoinApplicationDslMarker
fun startKoin(appDeclaration: KoinAppDeclaration): KoinApplication = KoinPlatformTools.defaultContext().startKoin(appDeclaration)
@KoinApplicationDslMarker
fun startKoin(appConfiguration: KoinConfiguration): KoinApplication = KoinPlatformTools.defaultContext().startKoin(appConfiguration.config)
/**
* Koin Application
* Help prepare resources for Koin context
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
@KoinApplicationDslMarker
class KoinApplication private constructor() {
val koin = Koin()
private var allowOverride = true
// ...
/**
* Load definitions from modules
* @param modules
*/
fun modules(modules: List<Module>): KoinApplication {
if (koin.logger.isAt(Level.INFO)) {
val duration = measureTime { loadModules(modules) }
val count = koin.instanceRegistry.size()
koin.logger.display(Level.INFO, "Started $count definitions in ${duration.inMs} ms")
} else {
loadModules(modules)
}
return this
}
private fun loadModules(modules: List<Module>) {
koin.loadModules(modules, allowOverride = allowOverride, createEagerInstances = false)
}
// ...
}
KoinApplication中主要就是 Koin 字段和 modules 方法。有注释可以知道,主要是为 Koin 对象准备一些资源。
那Koin 中又主要有什么内容呢?
/**
* Koin
*
* Gather main features to use on Koin context
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
class Koin {
// ===============> 1. 实际存储各个module内容的对象
@KoinInternalApi
val scopeRegistry = ScopeRegistry(this) // 用于存储Scope
@KoinInternalApi
val instanceRegistry = InstanceRegistry(this) // 用于存储实例
@KoinInternalApi
val propertyRegistry = PropertyRegistry(this)
@KoinInternalApi
val extensionManager = ExtensionManager(this)
@KoinInternalApi
var logger: Logger = EmptyLogger()
private set
@KoinInternalApi
fun setupLogger(logger: Logger) {
this.logger = logger
}
// ====================> 2. 获取对应实例的方法
/**
* Lazy inject a Koin instance
* @param qualifier
* @param scope
* @param parameters
*
* @return Lazy instance of type T
*/
inline fun <reified T : Any> inject(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<T> = scopeRegistry.rootScope.inject(qualifier, mode, parameters)
/**
* Lazy inject a Koin instance if available
* @param qualifier
* @param scope
* @param parameters
*
* @return Lazy instance of type T or null
*/
inline fun <reified T : Any> injectOrNull(
qualifier: Qualifier? = null,
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<T?> = scopeRegistry.rootScope.injectOrNull(qualifier, mode, parameters)
/**
* Get a Koin instance
* @param qualifier
* @param scope
* @param parameters
*/
inline fun <reified T : Any> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T = scopeRegistry.rootScope.get(qualifier, parameters)
// ...
/**
* Load module & create eager instances
*
* @param allowOverride - allow to override definitions
* @param createEagerInstances - run instance creation for eager single definitions
*/
fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances : Boolean = false) {
val flattedModules = flatten(modules)
instanceRegistry.loadModules(flattedModules, allowOverride)
scopeRegistry.loadScopes(flattedModules)
if (createEagerInstances){
createEagerInstances()
}
}
//......
}
KoinApplication.modules
startKoin {
//......
modules(factoryModule)
}
// KoinApplication
/**
* Load definitions from modules
* @param modules
*/
fun modules(modules: List<Module>): KoinApplication {
if (koin.logger.isAt(Level.INFO)) {
val duration = measureTime { loadModules(modules) }
val count = koin.instanceRegistry.size()
koin.logger.display(Level.INFO, "Started $count definitions in ${duration.inMs} ms")
} else {
loadModules(modules)
}
return this
}
private fun loadModules(modules: List<Module>) {
koin.loadModules(modules, allowOverride = allowOverride, createEagerInstances = false)
}
// Koin
/**
* Load module & create eager instances
*
* @param allowOverride - allow to override definitions
* @param createEagerInstances - run instance creation for eager single definitions
*/
fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances : Boolean = false) {
val flattedModules = flatten(modules)
instanceRegistry.loadModules(flattedModules, allowOverride)
scopeRegistry.loadScopes(flattedModules)
if (createEagerInstances){
createEagerInstances()
}
}
InstanceRegistry 如下:
@Suppress("UNCHECKED_CAST")
@OptIn(KoinInternalApi::class)
class InstanceRegistry(val _koin: Koin) {
private val _instances = safeHashMap<IndexKey, InstanceFactory<*>>()
val instances: Map<IndexKey, InstanceFactory<*>>
get() = _instances
private val eagerInstances = safeHashMap<Int, SingleInstanceFactory<*>>()
internal fun loadModules(modules: Set<Module>, allowOverride: Boolean) {
modules.forEach { module ->
loadModule(module, allowOverride)
addAllEagerInstances(module)
}
}
private fun addAllEagerInstances(module: Module) {
module.eagerInstances.forEach { factory ->
eagerInstances[factory.beanDefinition.hashCode()] = factory
}
}
private fun loadModule(module: Module, allowOverride: Boolean) {
module.mappings.forEach { (mapping, factory) ->
saveMapping(allowOverride, mapping, factory)
}
}
// ...
@KoinInternalApi
fun saveMapping(
allowOverride: Boolean,
mapping: IndexKey,
factory: InstanceFactory<*>,
logWarning: Boolean = true,
) {
_instances[mapping]?.let {
if (!allowOverride) {
overrideError(factory, mapping)
} else if (logWarning) {
_koin.logger.warn("(+) override index '$mapping' -> '${factory.beanDefinition}'")
}
}
_koin.logger.debug("(+) index '$mapping' -> '${factory.beanDefinition}'")
_instances[mapping] = factory
}
internal fun resolveDefinition(
clazz: KClass<*>,
qualifier: Qualifier?,
scopeQualifier: Qualifier,
): InstanceFactory<*>? {
val indexKey = indexKey(clazz, qualifier, scopeQualifier)
return _instances[indexKey]
}
// ...
}
从上述的源码可以知道,我们在调用modules后,实际上是遍历所有我们传入的Module对象,然后继续根据Module对象中 mappings 集合,分别将我们声明的实例化逻辑添加到Map中。那 mappings 、mapping: IndexKey 以及 factory: InstanceFactory<*> 是什么东西呢?它们在什么时候被赋值的?我们接着往下看。
Module
先回忆一下我们是如何声明一个Module来管理我们的依赖对象的
// normalMoudle就是来管理常规的对象注入
val normalModule = module {
factory { KoinTest() }
}
// singleModule则是用来单例对象注入
val singleModule = module {
single { SingletonTest() }
}
/**
* Define a Module
* @param createdAtStart
*
*/
@KoinDslMarker
fun module(createdAtStart: Boolean = false, moduleDeclaration: ModuleDeclaration): Module {
val module = Module(createdAtStart)
moduleDeclaration(module)
return module
}
Module类如下:
/**
* Koin Module
* Gather/help compose Koin definitions
*
* @author Arnaud Giuliani
*/
@OptIn(KoinInternalApi::class)
@KoinDslMarker
class Module(
@PublishedApi
internal val _createdAtStart: Boolean = false,
) {
val id = KoinPlatformTools.generateId()
var eagerInstances = LinkedHashSet<SingleInstanceFactory<*>>()
internal set
@KoinInternalApi
val mappings = LinkedHashMap<IndexKey, InstanceFactory<*>>()
// ...
// =========================> 1
/**
* Declare a Single definition
* @param qualifier
* @param createdAtStart
* @param definition - definition function
*/
inline fun <reified T> single(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
noinline definition: Definition<T>,
): KoinDefinition<T> {
val factory = _singleInstanceFactory(qualifier, definition)
indexPrimaryType(factory)
if (createdAtStart || this._createdAtStart) {
prepareForCreationAtStart(factory)
}
return KoinDefinition(this, factory)
}
@KoinInternalApi
fun indexPrimaryType(instanceFactory: InstanceFactory<*>) {
val def = instanceFactory.beanDefinition
val mapping = indexKey(def.primaryType, def.qualifier, def.scopeQualifier)
saveMapping(mapping, instanceFactory)
}
@PublishedApi
internal fun saveMapping(mapping: IndexKey, factory: InstanceFactory<*>) {
mappings[mapping] = factory
}
/**
* Declare a Factory definition
* @param qualifier
* @param definition - definition function
*/
inline fun <reified T> factory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>,
): KoinDefinition<T> {
return factory(qualifier, definition, rootScopeQualifier)
}
// =========================> 2
@PublishedApi
internal inline fun <reified T> factory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>,
scopeQualifier: Qualifier,
): KoinDefinition<T> {
val factory = _factoryInstanceFactory(qualifier, definition, scopeQualifier)
indexPrimaryType(factory)
return KoinDefinition(this, factory)
}
}
@KoinInternalApi
inline fun <reified T> _singleInstanceFactory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>,
scopeQualifier: Qualifier = rootScopeQualifier,
): SingleInstanceFactory<T> {
val def = _createDefinition(Kind.Singleton, qualifier, definition, scopeQualifier = scopeQualifier)
return SingleInstanceFactory(def)
}
@KoinInternalApi
inline fun <reified T> _factoryInstanceFactory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>,
scopeQualifier: Qualifier = rootScopeQualifier,
): FactoryInstanceFactory<T> {
val def = _createDefinition(Kind.Factory, qualifier, definition, scopeQualifier = scopeQualifier)
return FactoryInstanceFactory(def)
}
@KoinInternalApi
inline fun <reified T> _scopedInstanceFactory(
qualifier: Qualifier? = null,
noinline definition: Definition<T>,
scopeQualifier: Qualifier,
): ScopedInstanceFactory<T> {
val def = _createDefinition(Kind.Scoped, qualifier, definition, scopeQualifier = scopeQualifier)
return ScopedInstanceFactory(def)
}
// BeanDefinition
inline fun indexKey(clazz: KClass<*>, typeQualifier: Qualifier?, scopeQualifier: Qualifier): String {
return buildString {
append(clazz.getFullName())
append(':')
append(typeQualifier?.value ?: "")
append(':')
append(scopeQualifier)
}
}
- single :每次注入对象都是不会变的,全局都是一个对象
- factory:每次注入都会生成一个新的对象
分别调用对应的方法创建 Factory对象,然后为InstanceFactory生成一个IndexKey并将它们作为KV存到mappings中,最后创建并返回 KoinDefinition。
到这我们就清楚,在前文XXXRegistry中提到的需要存储的IndexKey和Factory就是这个地方创建并且暂时存储到 mappings 中,用于后续过程的遍历使用。
分析到这,我们大致了解了 Koin 是如何声明并且存储创建实例的工厂逻辑的。那么我们在调用注入函数时,是如何创建实例的呢?
inject()/get()
在 Android 中,我们在调用inject()或get()方法后,实际上就是会走到下面这个逻辑。
@OptIn(KoinInternalApi::class)
inline fun <reified T : Any> ComponentCallbacks.get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null,
): T {
return getKoinScope().get(qualifier, parameters)
}
获取正确的Scope,从中获取实例。Scope又是什么呢?
Scope
@Suppress("UNCHECKED_CAST")
@OptIn(KoinInternalApi::class)
@KoinDslMarker
class Scope(
val scopeQualifier: Qualifier,
val id: ScopeID,
val isRoot: Boolean = false,
@PublishedApi
internal val _koin: Koin,
) : Lockable() {
private val linkedScopes = LinkedHashSet<Scope>()
@KoinInternalApi
var sourceValue: Any? = null
val closed: Boolean
get() = _closed
inline fun isNotClosed() = !closed
private val _callbacks = LinkedHashSet<ScopeCallback>()
@KoinInternalApi
private var parameterStack: ThreadLocal<ArrayDeque<ParametersHolder>>? = null
private var _closed: Boolean = false
val logger: Logger get() = _koin.logger
internal fun create(links: List<Scope>) {
linkedScopes.addAll(links)
}
/**
* Get a Koin instance
* @param clazz
* @param qualifier
* @param parameters
*
* @return instance of type T
*/
fun <T> get(
clazz: KClass<*>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null,
): T {
return resolve(clazz, qualifier,parameters?.invoke())
}
// =======================> 最终都会调用这个方法
private fun <T> resolve(
clazz: KClass<*>,
qualifier: Qualifier?,
parameters: ParametersHolder? = null
): T {
if (!_koin.logger.isAt(Level.DEBUG)) {
return resolveInstance(qualifier, clazz, parameters)
}
// ...
val result = measureTimedValue { resolveInstance<T>(qualifier, clazz, parameters) }
// ...
return result.value
}
private fun <T> resolveInstance(
qualifier: Qualifier?,
clazz: KClass<*>,
parameters: ParametersHolder?,
): T {
//...
val instanceContext = ResolutionContext(_koin.logger, this, clazz, qualifier, parameters)
return stackParametersCall(parameters, instanceContext)
}
private fun onParameterOnStack(parameters: ParametersHolder): ArrayDeque<ParametersHolder> {
val stack = getOrCreateParameterStack()
stack.addFirst(parameters)
return stack
}
private fun <T> resolveFromContext(
instanceContext: ResolutionContext
): T {
return resolveFromInjectedParameters(instanceContext)
?: resolveFromRegistry(instanceContext) // =========> 我们主要看这个,会从我们之前提到的xxxxRegistry中获取
?: resolveFromStackedParameters(instanceContext)
?: resolveFromScopeSource(instanceContext)
?: resolveFromParentScopes(instanceContext)
?: throwNoDefinitionFound(instanceContext)
}
private fun <T> resolveFromRegistry(
ctx: ResolutionContext
): T? {
return _koin.instanceRegistry.resolveInstance(ctx.qualifier, ctx.clazz, this.scopeQualifier, ctx)
}
private inline fun <T> resolveFromInjectedParameters(ctx: ResolutionContext): T? {
return if (ctx.parameters == null) null
else {
_koin.logger.debug("|- ? ${ctx.debugTag} look in injected parameters")
ctx.parameters.getOrNull(clazz = ctx.clazz)
}
}
private inline fun <T> resolveFromStackedParameters(ctx: ResolutionContext): T? {
val current = parameterStack?.get()
return if (current.isNullOrEmpty()) null
else {
_koin.logger.debug("|- ? ${ctx.debugTag} look in stack parameters")
val parameters = current.firstOrNull()
parameters?.getOrNull(ctx.clazz)
}
}
private inline fun <T> resolveFromScopeSource(ctx: ResolutionContext): T? {
if (isRoot) return null
_koin.logger.debug("|- ? ${ctx.debugTag} look at scope source")
return if (ctx.clazz.isInstance(sourceValue) && ctx.qualifier == null) { sourceValue as? T } else null
}
private fun <T> resolveFromParentScopes(ctx: ResolutionContext): T? {
_koin.logger.debug("|- ? ${ctx.debugTag} look in other scopes")
return findInOtherScope(ctx)
}
private fun <T> findInOtherScope(
ctx: ResolutionContext,
): T? {
return linkedScopes.firstNotNullOfOrNull { it.getOrNull(ctx) }
}
}
在上述的代码可以看到,会依次从下列不同的地方尝试获取实例。
private fun <T> resolveFromContext(
instanceContext: ResolutionContext
): T {
return resolveFromInjectedParameters(instanceContext)
?: resolveFromRegistry(instanceContext) // 我们主要看这个,会从我们之前提到的xxxxRegistry中获取
?: resolveFromStackedParameters(instanceContext)
?: resolveFromScopeSource(instanceContext)
?: resolveFromParentScopes(instanceContext)
?: throwNoDefinitionFound(instanceContext)
}
private fun <T> resolveFromRegistry(
ctx: ResolutionContext
): T? {
return _koin.instanceRegistry.resolveInstance(ctx.qualifier, ctx.clazz, this.scopeQualifier, ctx)
}
// InstanceRegistry
internal fun resolveDefinition(
clazz: KClass<*>,
qualifier: Qualifier?,
scopeQualifier: Qualifier,
): InstanceFactory<*>? {
val indexKey = indexKey(clazz, qualifier, scopeQualifier)
return _instances[indexKey]
}
// 其实这个地方就是使用下面的规则来标识实例
// 全类名 + typeQualifier(类别限定,防止在同一scope下出现多个相同的实例) + ScopeId
inline fun indexKey(clazz: KClass<*>, typeQualifier: Qualifier?, scopeQualifier: Qualifier): String {
return buildString {
append(clazz.getFullName())
append(':')
append(typeQualifier?.value ?: "")
append(':')
append(scopeQualifier)
}
}
还有一个问题,没有解决——怎么实现single{}和factory{}的?
其实也容易想到:在一个地方缓存下创建过的实例来实现Singleton。
这张图片就是继承InstanceFactory来实现不同功能的类。SingleInstanceFactory类中就包含 value 字段用于存储实例。
到这里,我们就大致的了解了Koin整体的运行逻辑。
总结
与Hilt的区别:
- 运行时注入:Koin 没有像Hilt一样,在编译阶段为提供者和接受者生成对应的中间代码,而是将整个设计到的中间代码都抽象了出来。提供者只需要创建Module,接受者只需要调用get() 来进行注入。存在类型转化的性能开销
- 支持提前创建实例:单例的创建能够在初始化时就进行创建
- 错误暴露滞后:因为是运行时,所以在只有在运行的时候才能发现一些错误