深入理解Koin框架之koin-core
深入理解Koin
[toc]
koin是使用kotlin编写的一款轻量级依赖注入(DI)框架,是Android开发领域依赖注入框架的后起之秀,与目前主流的依赖注入框架Dagger2相比,它的原理更容易理解和掌握,大有取代Dagger2的趋势。本文将从实践经验出发,深入浅出的介绍koin的使用技巧和自己对koin源码的理解,希望可以为读者解决一些日常开发中的遇到问题和带来一些对koin框架原理的较为深入认识。由于是基于个人的认知和理解水平,如有疏漏和误解,恳请各位读者指正
一、Koin简介
koin是github上的一个开源项目,项目地址如下:https://github.com/InsertKoinIO/koin。本部分的内容主要来自该项目的描述。与Dagger编译时自动生成注入代码不一样的是,koin是不生成模版代码,而是使用一套DSL来定义对象的注入,因为,整个注入逻辑都可以通过源代码直观的反映出来,而Dagger2的注解方式隐含的逻辑的太多,学习成本比较搞,各种注解的作用,生成的代码的可读性等都是使用Dagger2的一些痛点,同时Dagger2不是kotlin编写,无法使用kotin的一些特性,在kotlin越来越流行的背景下,koin框架也越来越得到开发人员的认可。
由于服务端的依赖注入主要是Spring框架,所以Koin的主要应用领域还是Android平台,但从koin框架的架构来看,它也可以用于服务端和JAVA环境开发。以koin-core和koin-core-ext为基础库,koin以扩展的方式提供了android(androidx)和 ktor的支持。
核心库
- koin-core
- koin-core-ext
android
- koin-android
- koin-android-ext
- koin-android-scope
- koin-android-viewmodel
androidx
- koin-androidx-ext
- koin-androidx-fragment
- koin-androidx-scope
- koin-androidx-viewmodel
ktor
- koin-ktor
koin整个工程的代码量不是很大,掌握起来不会有太大的难度。后续内容会详细介绍上述模块,同时会从实践的角度解读这些代码。
二、使用入门
koin作为一个应用框架,最重要的是了解如何使用,只有在逐步使用的过程中,随着遇到的问题一步一步的才产生对掌握原理的需求。因此,将使用入门放到正文的最前面,希望用简单的代码感性的认识一下koin是一个什么框架以及如何使用koin
2.1 依赖声明
目前koin的版本是2.1.5,以gradle的依赖声明方式来声明引入koin。引入核心包就可以开发的JAVA应用,如果是android,需要声明android扩展依赖,ktor开发同理
ext.koin_version = '2.1.5'
//引入核心包
implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-core-ext:$koin_version"
//如果是android开发,引入下面的android扩展包
//implementation "org.koin:koin-android:$koin_version"
//implementation "org.koin:koin-android-scope:$koin_version"
//implementation "org.koin:koin-android-viewmodel:$koin_version"
//implementation "org.koin:koin-android-ext:$koin_version"
//引入androidx扩展
//implementation "org.koin:koin-androidx-scope:$koin_version"
//implementation "org.koin:koin-androidx-viewmodel:$koin_version"
//implementation "org.koin:koin-androidx-fragment:$koin_version"
//implementation "org.koin:koin-androidx-ext:$koin_version"
//如果是开发ktor应用,引入ktor扩展
//implementation "org.koin:koin-ktor:$koin_version"
2.2 例程
下面通过对源码中的例子coffee-maker来讲解一下koin的注入流程.
coffee-maker这个应用的逻辑很简单,有一家咖啡店(CoffeeApp)有一部咖啡机(CoffeeMaker),它可以煮咖啡(brew),同时咖啡机由加热器(Heater-接口),泵(Pump-接口)组成,在本例中加热器是一个电子加热器(ElectricHeater-实现Heater),泵是一个热虹吸式再沸器(Thermosiphon-实现Pump),因此抽象出以下基础模型:
interface Heater {
fun on()
fun off()
fun isHot(): Boolean
}
class ElectricHeater : Heater {
private var heating: Boolean = false
override fun on() {
println("~ ~ ~ heating ~ ~ ~")
heating = true
}
override fun off() {
heating = false
}
override fun isHot(): Boolean = heating
}
interface Pump {
fun pump()
}
class Thermosiphon(private val heater: Heater) : Pump {
override fun pump() {
if (heater.isHot()) {
println("=> => pumping => =>")
}
}
}
class CoffeeMaker(private val pump: Pump, private val heater: Heater) {
fun brew() {
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
上面的三类模型是我们煮一杯咖啡需要用的对象,DI做目的是将类的对象生成和对象使用分离,因为类的对象生成通常是比较复杂且变动比较频繁。因此DI框架来负责对象生成,开发人员只需要告诉DI框架,我需要那种类型的对象即可,可以极大的减少开发人员的工作量。因此对于咖啡店(CoffeeApp)的来说,他只需要一台咖啡机。而咖啡机怎么组装的,有哪些零部件,他并不关心。
class CoffeeApp : KoinComponent {
val maker: CoffeeMaker by inject()
}
这里的maker(咖啡机)不直接调用CoffeeMaker构造方法(因为调用构造方法还得先构造出Heater 和 Pump对象,太复杂了),而是使用by inject() 来向框架要一个CoffeeMaker 对象。拿到对象之后就可以调用CoffeeMaker的brew方法来煮咖啡了。
我们来看以下煮一杯咖啡的完整流程:
fun main() {
startKoin {
printLogger()
modules(listOf(coffeeAppModule))
}
val coffeeShop = CoffeeApp()
coffeeShop.maker.brew()
stopKoin()
}
startKoin 函数是启动koin框架的入口,传入一个定义在KoinApplication上的一个扩展lamda类型KoinAppDeclaration来配置koin。
modules 是KoinApplication的一个成员函数,来配置这个koinApplication有哪些module,本例中传入listOf(coffeeAppModule)是配置依赖的地方。
val coffeeAppModule = module {
single { CoffeeMaker(get(), get()) }
single<Pump> { Thermosiphon(get()) }
single<Heater> { ElectricHeater() }
}
module 是一个顶层函数,接受一个定义在Module上的扩展lamda类型ModuleDeclaration来配置这个module
single 是Module的一个成员方法,它接受一个定义在Scope上的扩展lamda类型Definition来配置在Scope(这里是rootScope)中生成一个具体的对象。single方法定义的对象在该scope中会缓存起来,多个地方依赖同一个对象
get是定义在Scope上的成员方法,自动进行类型推断。这里将是从rootScope中查找对应的对象定义,CoffeeMaker(get(), get())中,第一个体get会去查找Pump类的对象,第二个会去查找Heater类对象。这个两个对象正好是接下来的两行注入的。
stopKoin() 也是一个顶层函数,用于停止koin,清理koin中的对象
这个例程示范了koin的基本用法,也是koin DSL的基本语法
startKoin {//启动koin
modules() //加载modules
}
module {//声明一个module
single {//单例对象,多次依赖只产生一个对象
Constructor()
}
factory {//工厂对象,每次依赖产生一个新对象
Constructor()
}
}
}
stopKoin() // 结束
三、 Koin进阶
相信看完例程后我们对koin有了一个比较感性的认识,它通过一个DSL来声明各个类如何生成对象(单例模式和工厂模式),如果一个类的构造方法需要依赖另外一个类的对象,则调用具有类型推断功能的get()从koin中查找这个对象即可。因此,这个module的DSL看起来非常的简洁。调用具有0个或者多个get()参数的构造方法即可。如另外一个例程中
fun perfModule400() = module {
single { A1() } // new A1()
single { B1(get()) }// new B1(A1)
single { C1(get(), get()) }// new C1(A1,B1)
single { D1(get(), get(), get()) } // new D1(A1,B1,C1)
single { A2() } //new A2()
single { B2(get()) } // new B2(A2)
single { C2(get(), get()) } // new C2(A2,B2)
}
这里我们用的是最简单的注入场景,实际中可能会存在更加复杂的注入场景。下面来说明以下
3.1 Qualifer
上面的例程中,一个类只声明了一种创建对象的方法,因此koin查找的时候自然就找到这个对象,但是如果一个类需要生成多种类型的对象时,koin就无法确定使用哪一个对象。此时就需要Qualifer来标记不同的对象,相当于为创建这种对象的方式起一个别名,这与Dagger2是相似的,知识Koin并不是使用注解来实现的,而是定义了一个Qualifier接口,并提供了两个实现:
- StringQualifier 以字符串为value的qualifier
- TypeQualifier 以类为type,以类的全限定名为value的qualifier
同时提供了多个顶层函数来生成Qualifier,支持字符串,枚举和Class作为value,如下调用都可以生成一个qualifier,常用的是StringQualifier
name("qualifer_name")
named<T>(enum<T>)
qualifier("qualifer_name")
qualifier<T>(enum<T>)
named<T>()
qualifier<T>()
为了更好的理解和使用qualifier,我们修改以下煮咖啡场景。假设现ElectricHeater 有两种规格,高功率和低功率的,现在咖啡店需要两台咖啡机,高低功率各一台。
class SizedElectricHeater(val size:Int) : ElectricHeater() {//extend ElectricHeater
override fun on() {
println("~ ~ ~ heating ~ ~ ~ with ${size} W")
heating = true
}
}
注入配置如下:
val coffeeAppModule = module {
single(name("high")) { CoffeeMaker(get(name("high")), get(name("high"))) }
single(name("low")) { CoffeeMaker(get(name("low")), get(name("low"))) }
single<Pump>(named("high")) { Thermosiphon(get(named("high"))) }
single<Pump>(named("low")) { Thermosiphon(get(named("low"))) }
single<Heater>(named("high")) { SizedElectricHeater(3000) }
single<Heater>(named("low")) { SizedElectricHeater(1000) }
}
class CoffeeApp : KoinComponent {
val lowMaker: CoffeeMaker by inject(named("low"))
val highMaker: CoffeeMaker by inject(named("high"))
}
named 和 qualifer 是可以混用的,他们返回的都是StringQualifier或者TypeQualifier
3.2 Scope
注入对象都是有作用范围的,如果没有指定scope的话就是koin的一个rootScope,如果指定scope,注入时就会从该scope中去查找声明的对象
如上面的例子,由于Heater有2种功率的产品,所以在在module里面Pump,CoffeeMaker都声明了high和low,我们可以使用scope来简化这种场景
val coffeeAppModule = module {
scope(named("low")) {
scoped {// this will be single
SizedElectricHeater(1000)
}
scoped {
Thermosiphon(get())
}
scoped {
CoffeeMaker(get<Thermosiphon>(), get<SizedElectricHeater>())
}
}
scope(named("high")) {
scoped {// this will be single
SizedElectricHeater(3000)
}.bind(Heater::class)
scoped {
Thermosiphon(get())
}.bind(Pump::class)
scoped {
CoffeeMaker(get(), get())
}
}
}
这里声明了两个scope, 他们的qualifier 是 name("low"),named("high"),这样配置之后,在使用这些对方的时候,可以按照下面的方式去获取
val lowMaker by getKoin().getOrCreateScope("lowScope", named("low")).inject<CoffeeMaker>();
val highMaker by getKoin().getOrCreateScope("highScope", named("high")).inject<CoffeeMaker>() ;
这里有几个新的函数需要说明一下
scope 是Module的成员函数,用于在一个模块内自定义scope,它接受一个qualifier来标志这个scope和一个定义在ScopeDSL上的一个lamda扩展函数,
scoped 是ScopeDSL的一个成员函数,用于定义一个single类型的实例
bind 是定义在BeanDefinition上的扩展函数,用于配置该对象的兼容的类
scoped {
Thermosiphon(get())
}.bind(Pump::class)
由于Thermosiphon是Pump的子类,而查询对象时,查找的是Pump的对象,因此Thermosiphon这个对象定义需要bind到Pump::class才能找到这个对象。
3.3 Parameter
在上面的例子中,因为Heater增加了一个功率参数,导致注入配置发生一系列的变化,那如果功率的参数是在获取对象的时候传入的话,就更加灵活。Koin也是是支持在获取对象的时候传入构造参数的。Koin的构造参数是通过DefinitionParameters来描述的,它支持最多穿5个参数
scope(named("parameter2")) {
scoped {
(size:Int) -> SizedElectricHeater(size)
}.bind(Heater::class)
scoped {
(size:Int) -> Thermosiphon(get<SizedElectricHeater> {
parametersOf(size)
})
}.bind(Pump::class)
scoped {
(size:Int) -> CoffeeMaker(get { parametersOf(size)},get { parametersOf(size)})
}
}
val paramteredMaker2 by getKoin().getOrCreateScope("param2", named("parameter2")).inject<CoffeeMaker> {
parametersOf(5500)
};
在这个scope中,定义了一个带参数的SizedElectricHeater,Thermosiphon构造的时候,通过通过
parametersOf传递构造参数。在最外层的app里面,才传递最终的参数5000.这里有两个语法。
- (size:Int) -> SizedElectricHeater(size)这个表达式是定义在Scope上的lamda扩展函数,用于定义参数格式
- parametersOf顶层函数通过一个vararg构造出一个DefinitionParameters类型的参数
3.4 Module
Module是一组关系较为密切的对象模块,它定义了一个模块内定义的对象的一些通用属性,主要有
- createAtStart :对象是否是在startKoin的时候就创建
- override: 当一个对象存在多个相同的定义时,后面的定义是否覆盖前面的定义
- rootScope:当没有指定scope时,使用rootScope,当指定的scope中没有查到到定义时,会在rootScope中继续查找
- otherScopes:自定义的scope列表,当调用module的成员方法scope的时候就可以创建一个自定义的scope
module提供两个成员方法,single 和 factory来配置注入的对象
single 相当于是单例模式,多次注入为同一个对象
factory 相当于工厂模式,每次注入一个新的对象
3.5 KoinComponent
KoinComponent 是对象注入的场所,通常业务场景里需要实现这个接口,当然也可以不实现而直接使用koin,因为它只是封装了的koin的几个方法,使得应用程序可方便的获取到注入的对象。总的来说,它有三个重要方法
get 立即从koin中获取指定的对象
inject 使用延迟加载从koin中获取指定的对象
bind 获取一个同时绑定了主类和次类的对象,返回次类对象
scope(named("high")) {
scoped {
SizedElectricHeater(3000)// 主类
}.bind(Heater::class)//次类
}
val highHeater = getKoin().getOrCreateScope("high", named("high"))
.bind<Heater,SizedElectricHeater>()
返回一个Heater类型的对象,它实际是一个SizedElectricHeater(3000)对象
总的来说,koin是非常精巧的一个注入框架,得益于kotlin语言的扩展函数,建立了一套注入的DSL,成功的摆脱了代码生成和反射这两种比较重的实现方式。在理解它的DSL基础上,整个框架的学习成本相对来说比较低。上面讲解的Scope,Qualifier,Module,KoinComponent和Parameter这几个重要概念,其中很多也是借鉴了Dagger2的架构,因此如果有Dagger2的开发经验的话,迁移到koin也是比较容易的。从应用的角度看,熟练使用上述的几个模型,就能满足大部分的业务开发需要。而下面开始,将要深入的源码里去深入理解koin,所有模型和它的DSL
四、原理分析
本章节主要基于源码来分析koin的原理,将围绕koin的运行状态,模块加载,Scope管理,DSL中各个模型的运行机制和对象注入流程这几个核心内容来讲解。
4.1 koin的运行状态
与koin的运行状态相关的类和方法如下:
- Koin 核心类,封装了内部关于module加载,scope管理,对象注入,属性注入等内部模块,对外暴露应用接口。
- KoinContext(实现类GlobalContext),koin的上下文,管理一个koin对象,负责koin对象的注册和关闭,应用中可以通过KoinContext来获取koin对象
- KoinApplication。负责配置一个koin,比如需要为koin配置哪些模块,输入哪些属性和日志平台配置,是koin的入口。
- KoinContextHandler koinContext的持有者单例对象,可以方便的获取一个注册的koinContext。
- ContextFunctions.kt 定义了启动koin的便捷顶层方法startKoin
koin的启动过程就是生成一个KoinApplication,它有一个koin成员变量,然后这个KoinAppliation会给koin装载一系列的module和属性,然后将这个koin交给一个KoinContext,最后将这个koinContext 存到KoinContextHandler,以便后续使用。
下面结合源码来讲解一下
- startKoin
fun startKoin(koinContext: KoinContext = GlobalContext(), appDeclaration: KoinAppDeclaration): KoinApplication {
KoinContextHandler.register(koinContext) //(1)
val koinApplication = KoinApplication.init()//(2)
KoinContextHandler.start(koinApplication)//(3)
appDeclaration(koinApplication) //(4)
koinApplication.createEagerInstances() //(5)
return koinApplication
}
首先生成一个koinContext,默认值为GlobalContext(), appDeclaration 是KoinApplication上的一个扩展lamda表达式,用于配置KoinApplication
typealias KoinAppDeclaration = KoinApplication.() -> Unit
(1) 将koinContext注册到KoinContextHandler,一个应用只能注册一次,只有一个koinContext,注册之后就可以在任何地方通过KoinContextHandler.getContext()取到这个koinContext
fun register(koinContext: KoinContext) = synchronized(this) {
if (_context != null) {
error("A KoinContext is already started")
}
_context = koinContext
}
(2)KoinApplication.init()是KoinApplication的一个静态工厂方法,返回一个初始化好的KoinApplication
companion object {
/**
* Create a new instance of KoinApplication
*/
@JvmStatic
fun init(): KoinApplication {
val app = KoinApplication()
app.init()
return app
}
}
Koin是KoinApplication的成员变量,在初始化KoinApplication的时候,生成Koin的对象,
init 时创建koin的rootScope定义
class KoinApplication private constructor() {
val koin = Koin() // 生成koin对象
internal fun init() {
koin._scopeRegistry.createRootScopeDefinition()// 创建rootScope定义
}
(3)KoinContextHandler.start(koinApplication) 将koinApplication与koinContext绑定,
fun start(koinApplication: KoinApplication) {
getContext().setup(koinApplication)
}
GlobalContext:
override fun setup(koinApplication: KoinApplication) = synchronized(this) {
if (_koin != null) {
throw KoinAppAlreadyStartedException("A Koin Application has already been started")
}
_koin = koinApplication.koin
}
(4)在koinApplication,Koin,KoinContext 都准备好之后,再回调appDeclaration(koinApplication)回到应用自己的配置逻辑,并将这个koinApplication传出去。下面就是对这个koinApplication的简单配置说明(printLogger 和modules 都是koinApplication的成员方法)
startKoin {
printLogger()// 使用System.out.print来输出日记
modules(listOf(coffeeAppModule))// 加载modules
}
(5)koinApplication.createEagerInstances(),应用配置按照自己的需要配置好koinapplication,接着又回到框架的逻辑,创建eager的对象(最终会进入到InstanceRegistry的createEagerInstances)
internal fun createEagerInstances() {
instances.values.filterIsInstance<SingleInstanceFactory<*>>()
.filter { instance -> instance.beanDefinition.options.isCreatedAtStart }
.forEach { instance ->
instance.get(
InstanceContext(_koin, _scope)
)
}
}
即将些配置了isCreateAtStart的对象,在startKoin的时候就提前创建好对象,相当于是预加载。到这里,koin的环境就完全搭建起来了,在需要注入对象的地方,可以直接通过koin的来获取。
4.2 模块加载
在上面KoinApplication的配置中说到,会调用koinApplition的modules来配置注入的模块,现在来看看加载这些模块的逻辑。
KoinApplication.modules
fun modules(modules: List<Module>): KoinApplication {
if (koin._logger.isAt(Level.INFO)) {
val duration = measureDuration {
loadModules(modules)
}
val count = koin._scopeRegistry.size()
koin._logger.info("loaded $count definitions - $duration ms")
} else {
loadModules(modules)//(1)
}
if (koin._logger.isAt(Level.INFO)) {
val duration = measureDuration {
koin.createRootScope()
}
koin._logger.info("create context - $duration ms")
} else {
koin.createRootScope() //(2)
}
return this
}
modules传入一个List<Module>,然后会调用loadModules 和 koin.createRootScope()
(1) KoinApplication.loadModules
private fun loadModules(modules: List<Module>) {
koin.loadModules(modules)
}
进入Koin.loadModules
fun loadModules(modules: List<Module>) = synchronized(this) {
_modules.addAll(modules)
_scopeRegistry.loadModules(modules)
}
首先,将modules都存入到koin的_modules字段中,然后接着调用_scopeRegistry.loadModules
ScopeRegistry.loadModules
internal fun loadModules(modules: Iterable<Module>) {
modules.forEach { module ->
if (!module.isLoaded) {
loadModule(module)
module.isLoaded = true
} else {
_koin._logger.error("module '$module' already loaded!")
}
}
}
循环调用ScopeRegistry.loadModule
private fun loadModule(module: Module) {
declareScope(module.rootScope)
declareScopes(module.otherScopes)
}
每加载一个module,会将个module相关的的rootScope和otherScopes为参数调用到declareScope。module的rootScope和otherScopes都是ScopeDenifition类型的对象,经过declare之后,会生成正在的Scope对象
ScopeRegistry. declareScope
private fun declareScope(scopeDefinition: ScopeDefinition) {
declareDefinitions(scopeDefinition)
declareInstances(scopeDefinition)
}
ScopeRegistry.declareDefinitions
private fun declareDefinitions(definition: ScopeDefinition) {
if (scopeDefinitions.contains(definition.qualifier.value)) {
mergeDefinitions(definition)
} else {
_scopeDefinitions[definition.qualifier.value] = definition.copy()
}
}
scopeDefinitions是scopeRegistry的属性,类型是Map<QualifierValue, ScopeDefinition>,如果已经存在一个qualifer相同的scopeDenifition,则进行合并,复杂将新的scopeDenifition存起来。mergeDefinitions的逻辑相对复杂。
ScopeRegistry.mergeDefinitions
private fun mergeDefinitions(definition: ScopeDefinition) {
val existing = scopeDefinitions[definition.qualifier.value]
?: error("Scope definition '$definition' not found in $_scopeDefinitions")
definition.definitions.forEach {
existing.save(it)
}
}
首先找到已经存在的那个scopeDenifition。scopeDenifition的主要结构是一个Set<BeanDenifition>,即这个scope中定义了哪些对象。合并的时候就是将传入的scopeDefinition中定义的对象save到已经存在的scopeDenifition中去.
BeanDenifition.save
fun save(beanDefinition: BeanDefinition<*>, forceOverride: Boolean = false) {
if (definitions.contains(beanDefinition)) {
if (beanDefinition.options.override || forceOverride) {
_definitions.remove(beanDefinition)
} else {
val current = definitions.firstOrNull { it == beanDefinition }
throw DefinitionOverrideException("Definition '$beanDefinition' try to override existing definition. Please use override option or check for definition '$current'")
}
}
_definitions.add(beanDefinition)
}
如果之前已经存在一个相同的beanDefinition的话,需要新的beanDenition是不是强制override或者module是强制override.如果是的话,移除旧的,然后加入新的BeanDefinition,否则会报异常DefinitionOverrideException。因此override是一个比较重要的配置。我们回到前面,看看beanDefinition.options.override 和 forceOverride 这两个的值是如何确定的。
(1) 首先是Module会进行全局的定义
class Module(
val createAtStart: Boolean,
val override: Boolean
)
例如:
val overridedModule = module(override = true) {
single {
ElectricHeater()
}
}
(2) 在BeanDenifion中,成员options中包含有override属性
data class BeanDefinition<T>(
val scopeDefinition: ScopeDefinition,
val primaryType: KClass<*>,
val qualifier: Qualifier? = null,
val definition: Definition<T>,
val kind: Kind,
val secondaryTypes: List<KClass<*>> = listOf(),
val options: Options = Options(),
val properties: Properties = Properties(),
val callbacks: Callbacks<T> = Callbacks()
)
data class Options(var isCreatedAtStart: Boolean = false, var override: Boolean = false)
而这个options是在mudule的single或者factory方法生成BeanDenifition的时候生成的
inline fun <reified T> single(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
override: Boolean = false,
noinline definition: Definition<T>
): BeanDefinition<T> {
return Definitions.saveSingle(
qualifier,
definition,
rootScope,
makeOptions(override, createdAtStart)
)
}
fun makeOptions(override: Boolean, createdAtStart: Boolean = false): Options =
Options(this.createAtStart || createdAtStart, this.override || override)
因此可以看到,override的值的确定逻辑是由module和single/factory方法共同决定,任何一个为true。则BeanDefinion的override为true
上面介绍的是ScopeRegistry.declareDefinitions,是ScopeRegistry.declareScope中的一个主要分支,这个分支,将Module中定义的的BeanDefinition放到了正确的ScopeDefinition中。第二个分支是ScopeRegistry.declareInstances(scopeDefinition),这个方法将BeanDefinition转换成具体的生成对象的工厂,在注入对象的时候,将会使用这些工厂来生成具体的实例。
ScopeRegistry.declareInstances
private fun declareInstances(scopeDefinition: ScopeDefinition) {
_scopes.values.filter { it._scopeDefinition == scopeDefinition }.forEach { it.loadDefinitions(scopeDefinition) }
}
_scopes是已经存在的Scope对象,初识时,scopes为空的,因此,在开始loadModules时,并不会出现scope中的BeanDefinition转换成工厂,但是如果已经存在这样的scope时,就会进入到Scope.loadDefinitions(scopeDefinition)
Scope.loadDefinitions:
fun loadDefinitions(scopeDefinition: ScopeDefinition) {
scopeDefinition.definitions.forEach {
_instanceRegistry.createDefinition(it)
}
}
_instanceRegistry是Scope的一个InstanceRegistry类型的成员变量,这里遍历scopeDefinition中定义的BeanDefinitions(definitions),调用_instanceRegistry.createDefinition来转换成InstantFactory
InstanceRegistry. createDefinition
internal fun createDefinition(definition: BeanDefinition<*>) {
saveDefinition(definition, false)
}
InstanceRegistry. saveDefinition
fun saveDefinition(definition: BeanDefinition<*>, override: Boolean) {
val defOverride = definition.options.override || override
val instanceFactory = createInstanceFactory(_koin, definition) //(1)
saveInstance(//(2)
indexKey(definition.primaryType, definition.qualifier),
instanceFactory,
defOverride
)
definition.secondaryTypes.forEach { clazz ->//(3)
if (defOverride) {
saveInstance(
indexKey(clazz, definition.qualifier),
instanceFactory,
defOverride
)
} else {
saveInstanceIfPossible(
indexKey(clazz, definition.qualifier),
instanceFactory
)
}
}
}
InstanceRegistry. createInstanceFactory
private fun createInstanceFactory(
_koin: Koin,
definition: BeanDefinition<*>
): InstanceFactory<*> {
return when (definition.kind) {
Kind.Single -> SingleInstanceFactory(_koin, definition)
Kind.Factory -> FactoryInstanceFactory(_koin, definition)
}
(1)首先,根据single还是factory方法定义的生成不同的InstantaceFactory。
(2)将工厂关联到主类
(3) 将工厂关联到所有的次类
至此,module的加载逻辑全部结束。需要注意的是,BeanDefinition转InstanceFactory的逻辑并不是每次都会执行的。如果scope不存在的话,这部分逻辑是不执行的。还有几个疑问是,BeanDefinition的属性比较多,比如primaryType和secondaryTypes 这两个字段对BeanDefinition转InstanceFactory都有影响,那他们是什么意思,如何配置的,这个也是比较重要的内容。但不管怎么说,我们loadModule的主线逻辑我们已经分析完毕。后面会再次补充细节一些的分支逻辑。
(2)在KoinApplication.loadModules结束后,接着做的是koin.createRootScope(),创建rootScope对象
Koin.createRootScope:
fun createRootScope() {
_scopeRegistry.createRootScope()
}
ScopeRegistry.createRootScope:
internal fun createRootScope() {
if (_rootScope == null) {
_rootScope =
createScope(ScopeDefinition.ROOT_SCOPE_ID, ScopeDefinition.ROOT_SCOPE_QUALIFIER, null)
}
}
ScopeRegistry. createScope:
fun createScope(scopeId: ScopeID, qualifier: Qualifier, source: Any? = null): Scope {
if (scopes.contains(scopeId)) {
throw ScopeAlreadyCreatedException("Scope with id '$scopeId' is already created")
}
val scopeDefinition = scopeDefinitions[qualifier.value]
return if (scopeDefinition != null) {
val createdScope: Scope = createScope(scopeId, scopeDefinition, source)
_scopes[scopeId] = createdScope//(1)
createdScope
} else {
throw NoScopeDefFoundException("No Scope Definition found for qualifer '${qualifier.value}'")
}
}
_scopes 是一个Map,新创建的Scope对象会存入到ScopeRegistry的_scopes里面
ScopeRegistry. createScope:
private fun createScope(scopeId: ScopeID, scopeDefinition: ScopeDefinition, source: Any?): Scope {
val scope = Scope(scopeId, scopeDefinition, _koin, source)
val links = _rootScope?.let { listOf(it) } ?: emptyList()
scope.create(links)
return scope
}
调用Scope的构造方法,然后将_rootScope作为links,调用scope的create方法
4.3 模块卸载
加载的module也可以卸载,卸载时,会将这个module从加载的module集合中移除,同时会将这个module中声明的所有的scope中的BeanDenifition,InstanceFactory都移除,并将module.isLoaded设置成false
fun unloadModules(modules: List<Module>) = synchronized(this) {
_scopeRegistry.unloadModules(modules)
_modules.removeAll(modules)
}
fun unloadModules(modules: List<Module>) = synchronized(this) {
_scopeRegistry.unloadModules(modules)
_modules.removeAll(modules)
}
fun unloadModules(modules: Iterable<Module>) {
modules.forEach {
unloadModules(it)
}
}
fun unloadModules(module: Module) {
val scopeDefinitions = module.otherScopes + module.rootScope
scopeDefinitions.forEach {
unloadDefinitions(it)
}
module.isLoaded = false
}
private fun unloadDefinitions(scopeDefinition: ScopeDefinition) {
unloadInstances(scopeDefinition)
_scopeDefinitions.values.firstOrNull { it == scopeDefinition }?.unloadDefinitions(scopeDefinition)
}
private fun unloadInstances(scopeDefinition: ScopeDefinition) {
_scopes.values.filter { it._scopeDefinition == scopeDefinition }.forEach { it.dropInstances(scopeDefinition) }
}
这里需要注意的是,卸载module时,module声明的scope对象和scopeDefinition对象仍然保留在ScopeRegistry中,只是这些scope中与这个module相关的BeanDefinition和InstanceFactory被清空了。
4.4 scope管理
关于Scope有两个概念,一个是ScopeDefinition,是关于scope的定义,这个是在module里面通过scope函数来定义的;另一是Scope,是根据ScopeDefinition生成的真正的Scope对象,koin 使用ScopeRegistry来管理Scope 和 ScopeDefinition,其中ScopeDefinition时在loadModule的时候注册的,在Module初始化时就会生成一个rootScope的ScopeDefinition,scope函数生成的会scopeDefinition放到module.otherScope.
Module初始化:
class Module(
val createAtStart: Boolean,
val override: Boolean
) {
val rootScope: ScopeDefinition = ScopeDefinition.rootDefinition()
......
Module.scope:
fun scope(qualifier: Qualifier, scopeSet: ScopeDSL.() -> Unit) {
val scopeDefinition = ScopeDefinition(qualifier)
ScopeDSL(scopeDefinition).apply(scopeSet)
otherScopes.add(scopeDefinition)
}
而真正生成Scope的地方是在需要注入对象的地方。总的说来有两个地方。一个是上文说到的,在koinApplication.loadModules结束后,会创建好rootScope,另外一个是手动调用koin.createScope等方法。在讲createRootScope的时候,已经讲述了createScope函数,这里就不在重复。这里主要讲一下Scope本身属性
- scopeId,String的别名,Scope的ID
- _source,Any,如果在scope中没有找到对象,此时返回_soure
- _linkedScope,关联的其他scope,主要是rootScope
- _instanceRegistry,解析好的对象工厂管理器
Scope包含了koin的主要业务逻辑,koin的很多接口也是委托到scope来实现。ScopeRegsitry是注册Scope的组件。因此要创建或者获取一个scope都是通过ScopeRegsitry来实现的,我们来看一下scope的获取逻辑
Koin.getOrCreateScope:
fun getOrCreateScope(scopeId: ScopeID, qualifier: Qualifier): Scope {
return _scopeRegistry.getScopeOrNull(scopeId) ?: createScope(scopeId, qualifier)
}
可以看到,标志一个scope需要两个维度,一个是scopeId,另外一个qualifier。首先以scopeId来查询是否有存在的scope
ScopeRegister.getScopeOrNull:
fun getScopeOrNull(scopeId: ScopeID): Scope? {
return scopes[scopeId]
}
在ScopeRegistry中用一个Map来存放已经创建的scope,key为ScopeID。如果没有找到,就创建
ScopeRegister.createScope:
fun createScope(scopeId: ScopeID, qualifier: Qualifier, source: Any? = null): Scope {
if (scopes.contains(scopeId)) {
throw ScopeAlreadyCreatedException("Scope with id '$scopeId' is already created")
}
val scopeDefinition = scopeDefinitions[qualifier.value]
return if (scopeDefinition != null) {
val createdScope: Scope = createScope(scopeId, scopeDefinition, source)
_scopes[scopeId] = createdScope
createdScope
} else {
throw NoScopeDefFoundException("No Scope Definition found for qualifer '${qualifier.value}'")
}
}
这段逻辑比较简单,如果已经存在相同的scopeId了,则抛出异常ScopeAlreadyCreatedException。否则以qualifer去查找scopeDefinition,如果存在这样的scopeDefinition,则用该scopeDefinition创建一个新的scope,然后记录下载并当作结果返回,否则报异常NoScopeDefFoundException,scopeId 对与scope本身来说是没有意义的。从这段代码我们可以看出,如果scopeId与第一个scope绑定后,如果这个scope删除后,是可以绑定到另外一个qualifiered的scope,即一个scopeId是有可能对应不同qualifer的scope的。使用时也需要注意,尽量不要出现这种情况。
scope也是可以被删除的
Koin.deleteScope:
fun deleteScope(scopeId: ScopeID) {
_scopeRegistry.deleteScope(scopeId)
}
ScopeRegister.deleteScope:
fun deleteScope(scopeId: ScopeID) {
_scopes.remove(scopeId)
}
4.6 Module 和Scope的关系
- scope由module来定义。也就是ScopeDefinition由module来生成
- scope对象一旦生成,就与module没有太大关系,因为scope 和scopeDefintion对象生成后,就交给ScopeRegistry管理,scopeRegistry是koin的成员变量,因此可以不局限于定义的module内,是koin全局共享的
4.7 对象查找过程
对象的查找逻辑最终是在scope中实现
Scope.get:
fun <T> get(
clazz: KClass<*>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null
): T {
return if (_koin._logger.isAt(Level.DEBUG)) {
val qualifierString = qualifier?.let { " with qualifier '$qualifier'" } ?: ""
_koin._logger.debug("+- '${clazz.getFullName()}'$qualifierString")
val (instance: T, duration: Double) = measureDurationForResult {
resolveInstance<T>(qualifier, clazz, parameters)
}
_koin._logger.debug("|- '${clazz.getFullName()}' in $duration ms")
return instance
} else {
resolveInstance(qualifier, clazz, parameters)
}
}
入参三个:
- 请求对象的class,
- 对象的限定符qulifier,可选,默认为null
- 生成对象的参数,可选,默认为null
然后会进入到resolveInstance函数
private fun <T> resolveInstance(
qualifier: Qualifier?,
clazz: KClass<*>,
parameters: ParametersDefinition?
): T {
if (_closed) {
throw ClosedScopeException("Scope '$id' is closed")
}
//TODO Resolve in Root or link
val indexKey = indexKey(clazz, qualifier)
return _instanceRegistry.resolveInstance(indexKey, parameters)
?: findInOtherScope<T>(clazz, qualifier, parameters) ?: getFromSource(clazz)
?: throwDefinitionNotFound(qualifier, clazz)
}
查找是由三个层级:
- 首先在当前的scope中查找,如果没有查找,则
- 在linkedScope中查找,默认是rootScope,如若还没有找到,则
- 尝试使用source对象,如果source的类型不一致则
- 抛出异常。
因此需要要注意的是,一个module里声明的对象,不能依赖到另外modudle中自定义scope中声明的对象,但是可以依赖到其他module中的rootScope中声明的对象。
val module1 = module {
single {
CoffeeMaker(get { parametersOf(7500) }, get { parametersOf(7500) })
}
}
val module3 = module(override = true) {
single { (size: Int) ->
SizedElectricHeater(size);
}.bind(Heater::class)
single { (size: Int) ->
Thermosiphon(get<SizedElectricHeater> {
parametersOf(size)
})
}.bind(Pump::class)
}
可以成功的获取到module1中定义的CoffeeMaker
val paramteredModuel2Maker = getKoin().get<CoffeeMaker>()
但是如果是这样:
val module1 = module {
single {
CoffeeMaker(get { parametersOf(7500) }, get { parametersOf(7500) })
}
}
val module2 = module() {
scope(named("module2")) {
scoped { (size: Int) ->
SizedElectricHeater(size);
}.bind(Heater::class)
}
scope(named("module2")) {
scoped { (size: Int) ->
Thermosiphon(get<SizedElectricHeater> {
parametersOf(size)
})
}.bind(Pump::class)
}
}
则不能找到CoffeeMaker的定义。
作如下修改:
val module1 = module {
single(named('module2')){
CoffeeMaker(get { parametersOf(7500) }, get { parametersOf(7500) })
}
}
就又可以通过如下的方式获取到CoffeeMaker对象
val paramteredModuel2Maker = getKoin().get<CoffeeMaker>(named("module2"))
因为在module1 和module2中都声明了named("module2")这个scope,在这个scope里声明了依赖的三个对象