深入理解Koin框架之koin-core

深入理解Koin

[toc]

koin是使用kotlin編寫的一款輕量級依賴注入(DI)框架,是Android開發(fā)領(lǐng)域依賴注入框架的后起之秀,與目前主流的依賴注入框架Dagger2相比,它的原理更容易理解和掌握,大有取代Dagger2的趨勢。本文將從實踐經(jīng)驗出發(fā),深入淺出的介紹koin的使用技巧和自己對koin源碼的理解,希望可以為讀者解決一些日常開發(fā)中的遇到問題和帶來一些對koin框架原理的較為深入認識。由于是基于個人的認知和理解水平,如有疏漏和誤解,懇請各位讀者指正

一、Koin簡介

koin是github上的一個開源項目,項目地址如下:https://github.com/InsertKoinIO/koin。本部分的內(nèi)容主要來自該項目的描述。與Dagger編譯時自動生成注入代碼不一樣的是,koin是不生成模版代碼,而是使用一套DSL來定義對象的注入,因為,整個注入邏輯都可以通過源代碼直觀的反映出來,而Dagger2的注解方式隱含的邏輯的太多,學(xué)習(xí)成本比較搞,各種注解的作用,生成的代碼的可讀性等都是使用Dagger2的一些痛點,同時Dagger2不是kotlin編寫,無法使用kotin的一些特性,在kotlin越來越流行的背景下,koin框架也越來越得到開發(fā)人員的認可。

由于服務(wù)端的依賴注入主要是Spring框架,所以Koin的主要應(yīng)用領(lǐng)域還是Android平臺,但從koin框架的架構(gòu)來看,它也可以用于服務(wù)端和JAVA環(huán)境開發(fā)。以koin-core和koin-core-ext為基礎(chǔ)庫,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整個工程的代碼量不是很大,掌握起來不會有太大的難度。后續(xù)內(nèi)容會詳細介紹上述模塊,同時會從實踐的角度解讀這些代碼。

二、使用入門

koin作為一個應(yīng)用框架,最重要的是了解如何使用,只有在逐步使用的過程中,隨著遇到的問題一步一步的才產(chǎn)生對掌握原理的需求。因此,將使用入門放到正文的最前面,希望用簡單的代碼感性的認識一下koin是一個什么框架以及如何使用koin

2.1 依賴聲明

目前koin的版本是2.1.5,以gradle的依賴聲明方式來聲明引入koin。引入核心包就可以開發(fā)的JAVA應(yīng)用,如果是android,需要聲明android擴展依賴,ktor開發(fā)同理

ext.koin_version = '2.1.5' 
//引入核心包
implementation "org.koin:koin-core:$koin_version"
implementation "org.koin:koin-core-ext:$koin_version"

//如果是android開發(fā),引入下面的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"

//如果是開發(fā)ktor應(yīng)用,引入ktor擴展
//implementation "org.koin:koin-ktor:$koin_version"

2.2 例程

下面通過對源碼中的例子coffee-maker來講解一下koin的注入流程.

coffee-maker這個應(yīng)用的邏輯很簡單,有一家咖啡店(CoffeeApp)有一部咖啡機(CoffeeMaker),它可以煮咖啡(brew),同時咖啡機由加熱器(Heater-接口),泵(Pump-接口)組成,在本例中加熱器是一個電子加熱器(ElectricHeater-實現(xiàn)Heater),泵是一個熱虹吸式再沸器(Thermosiphon-實現(xiàn)Pump),因此抽象出以下基礎(chǔ)模型:

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做目的是將類的對象生成和對象使用分離,因為類的對象生成通常是比較復(fù)雜且變動比較頻繁。因此DI框架來負責(zé)對象生成,開發(fā)人員只需要告訴DI框架,我需要那種類型的對象即可,可以極大的減少開發(fā)人員的工作量。因此對于咖啡店(CoffeeApp)的來說,他只需要一臺咖啡機。而咖啡機怎么組裝的,有哪些零部件,他并不關(guān)心。

class CoffeeApp : KoinComponent {
    val maker: CoffeeMaker by inject()
}

這里的maker(咖啡機)不直接調(diào)用CoffeeMaker構(gòu)造方法(因為調(diào)用構(gòu)造方法還得先構(gòu)造出Heater 和 Pump對象,太復(fù)雜了),而是使用by inject() 來向框架要一個CoffeeMaker 對象。拿到對象之后就可以調(diào)用CoffeeMaker的brew方法來煮咖啡了。

我們來看以下煮一杯咖啡的完整流程:

fun main() {
    startKoin {
        printLogger()
        modules(listOf(coffeeAppModule))
    }
    val coffeeShop = CoffeeApp()
    coffeeShop.maker.brew()
    stopKoin()
}

startKoin 函數(shù)是啟動koin框架的入口,傳入一個定義在KoinApplication上的一個擴展lamda類型KoinAppDeclaration來配置koin。
modules 是KoinApplication的一個成員函數(shù),來配置這個koinApplication有哪些module,本例中傳入listOf(coffeeAppModule)是配置依賴的地方。

val coffeeAppModule = module {
    single { CoffeeMaker(get(), get()) }
    single<Pump> { Thermosiphon(get()) }
    single<Heater> { ElectricHeater() }
}

module 是一個頂層函數(shù),接受一個定義在Module上的擴展lamda類型ModuleDeclaration來配置這個module

single 是Module的一個成員方法,它接受一個定義在Scope上的擴展lamda類型Definition來配置在Scope(這里是rootScope)中生成一個具體的對象。single方法定義的對象在該scope中會緩存起來,多個地方依賴同一個對象

get是定義在Scope上的成員方法,自動進行類型推斷。這里將是從rootScope中查找對應(yīng)的對象定義,CoffeeMaker(get(), get())中,第一個體get會去查找Pump類的對象,第二個會去查找Heater類對象。這個兩個對象正好是接下來的兩行注入的。

stopKoin() 也是一個頂層函數(shù),用于停止koin,清理koin中的對象

這個例程示范了koin的基本用法,也是koin DSL的基本語法

startKoin {//啟動koin
    modules() //加載modules
}
  module  {//聲明一個module
    single {//單例對象,多次依賴只產(chǎn)生一個對象
        Constructor()
    }
    factory {//工廠對象,每次依賴產(chǎn)生一個新對象
        Constructor()
    }
}
}
stopKoin() // 結(jié)束

三、 Koin進階

相信看完例程后我們對koin有了一個比較感性的認識,它通過一個DSL來聲明各個類如何生成對象(單例模式和工廠模式),如果一個類的構(gòu)造方法需要依賴另外一個類的對象,則調(diào)用具有類型推斷功能的get()從koin中查找這個對象即可。因此,這個module的DSL看起來非常的簡潔。調(diào)用具有0個或者多個get()參數(shù)的構(gòu)造方法即可。如另外一個例程中

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

這里我們用的是最簡單的注入場景,實際中可能會存在更加復(fù)雜的注入場景。下面來說明以下

3.1 Qualifer

上面的例程中,一個類只聲明了一種創(chuàng)建對象的方法,因此koin查找的時候自然就找到這個對象,但是如果一個類需要生成多種類型的對象時,koin就無法確定使用哪一個對象。此時就需要Qualifer來標(biāo)記不同的對象,相當(dāng)于為創(chuàng)建這種對象的方式起一個別名,這與Dagger2是相似的,知識Koin并不是使用注解來實現(xiàn)的,而是定義了一個Qualifier接口,并提供了兩個實現(xiàn):

  • StringQualifier 以字符串為value的qualifier
  • TypeQualifier 以類為type,以類的全限定名為value的qualifier

同時提供了多個頂層函數(shù)來生成Qualifier,支持字符串,枚舉和Class作為value,如下調(diào)用都可以生成一個qualifier,常用的是StringQualifier

name("qualifer_name")
named<T>(enum<T>)
qualifier("qualifer_name")
qualifier<T>(enum<T>)
named<T>()
qualifier<T>()

為了更好的理解和使用qualifier,我們修改以下煮咖啡場景。假設(shè)現(xiàn)ElectricHeater 有兩種規(guī)格,高功率和低功率的,現(xiàn)在咖啡店需要兩臺咖啡機,高低功率各一臺。

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"))
}

namedqualifer 是可以混用的,他們返回的都是StringQualifier或者TypeQualifier

3.2 Scope

注入對象都是有作用范圍的,如果沒有指定scope的話就是koin的一個rootScope,如果指定scope,注入時就會從該scope中去查找聲明的對象
如上面的例子,由于Heater有2種功率的產(chǎn)品,所以在在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>() ;

這里有幾個新的函數(shù)需要說明一下

scope 是Module的成員函數(shù),用于在一個模塊內(nèi)自定義scope,它接受一個qualifier來標(biāo)志這個scope和一個定義在ScopeDSL上的一個lamda擴展函數(shù),
scoped 是ScopeDSL的一個成員函數(shù),用于定義一個single類型的實例
bind 是定義在BeanDefinition上的擴展函數(shù),用于配置該對象的兼容的類

 scoped {
       Thermosiphon(get())
 }.bind(Pump::class)

由于ThermosiphonPump的子類,而查詢對象時,查找的是Pump的對象,因此Thermosiphon這個對象定義需要bindPump::class才能找到這個對象。

3.3 Parameter

在上面的例子中,因為Heater增加了一個功率參數(shù),導(dǎo)致注入配置發(fā)生一系列的變化,那如果功率的參數(shù)是在獲取對象的時候傳入的話,就更加靈活。Koin也是是支持在獲取對象的時候傳入構(gòu)造參數(shù)的。Koin的構(gòu)造參數(shù)是通過DefinitionParameters來描述的,它支持最多穿5個參數(shù)

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中,定義了一個帶參數(shù)的SizedElectricHeaterThermosiphon構(gòu)造的時候,通過通過
parametersOf傳遞構(gòu)造參數(shù)。在最外層的app里面,才傳遞最終的參數(shù)5000.這里有兩個語法。

  • (size:Int) -> SizedElectricHeater(size)這個表達式是定義在Scope上的lamda擴展函數(shù),用于定義參數(shù)格式
  • parametersOf頂層函數(shù)通過一個vararg構(gòu)造出一個DefinitionParameters類型的參數(shù)
3.4 Module

Module是一組關(guān)系較為密切的對象模塊,它定義了一個模塊內(nèi)定義的對象的一些通用屬性,主要有

  • createAtStart :對象是否是在startKoin的時候就創(chuàng)建
  • override: 當(dāng)一個對象存在多個相同的定義時,后面的定義是否覆蓋前面的定義
  • rootScope:當(dāng)沒有指定scope時,使用rootScope,當(dāng)指定的scope中沒有查到到定義時,會在rootScope中繼續(xù)查找
  • otherScopes:自定義的scope列表,當(dāng)調(diào)用module的成員方法scope的時候就可以創(chuàng)建一個自定義的scope

module提供兩個成員方法,singlefactory來配置注入的對象

single 相當(dāng)于是單例模式,多次注入為同一個對象

factory 相當(dāng)于工廠模式,每次注入一個新的對象

3.5 KoinComponent

KoinComponent 是對象注入的場所,通常業(yè)務(wù)場景里需要實現(xiàn)這個接口,當(dāng)然也可以不實現(xiàn)而直接使用koin,因為它只是封裝了的koin的幾個方法,使得應(yīng)用程序可方便的獲取到注入的對象。總的來說,它有三個重要方法

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語言的擴展函數(shù),建立了一套注入的DSL,成功的擺脫了代碼生成和反射這兩種比較重的實現(xiàn)方式。在理解它的DSL基礎(chǔ)上,整個框架的學(xué)習(xí)成本相對來說比較低。上面講解的Scope,Qualifier,Module,KoinComponent和Parameter這幾個重要概念,其中很多也是借鑒了Dagger2的架構(gòu),因此如果有Dagger2的開發(fā)經(jīng)驗的話,遷移到koin也是比較容易的。從應(yīng)用的角度看,熟練使用上述的幾個模型,就能滿足大部分的業(yè)務(wù)開發(fā)需要。而下面開始,將要深入的源碼里去深入理解koin,所有模型和它的DSL

四、原理分析

本章節(jié)主要基于源碼來分析koin的原理,將圍繞koin的運行狀態(tài),模塊加載,Scope管理,DSL中各個模型的運行機制和對象注入流程這幾個核心內(nèi)容來講解。

4.1 koin的運行狀態(tài)

與koin的運行狀態(tài)相關(guān)的類和方法如下:

  • Koin 核心類,封裝了內(nèi)部關(guān)于module加載,scope管理,對象注入,屬性注入等內(nèi)部模塊,對外暴露應(yīng)用接口。
  • KoinContext(實現(xiàn)類GlobalContext),koin的上下文,管理一個koin對象,負責(zé)koin對象的注冊和關(guān)閉,應(yīng)用中可以通過KoinContext來獲取koin對象
  • KoinApplication。負責(zé)配置一個koin,比如需要為koin配置哪些模塊,輸入哪些屬性和日志平臺配置,是koin的入口。
  • KoinContextHandler koinContext的持有者單例對象,可以方便的獲取一個注冊的koinContext。
  • ContextFunctions.kt 定義了啟動koin的便捷頂層方法startKoin

koin的啟動過程就是生成一個KoinApplication,它有一個koin成員變量,然后這個KoinAppliation會給koin裝載一系列的module和屬性,然后將這個koin交給一個KoinContext,最后將這個koinContext 存到KoinContextHandler,以便后續(xù)使用。

下面結(jié)合源碼來講解一下

  • 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,一個應(yīng)用只能注冊一次,只有一個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的一個靜態(tài)工廠方法,返回一個初始化好的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 時創(chuàng)建koin的rootScope定義

    class KoinApplication private constructor() {

        val koin = Koin() // 生成koin對象

        internal fun  init() {
            koin._scopeRegistry.createRootScopeDefinition()// 創(chuàng)建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 都準(zhǔn)備好之后,再回調(diào)appDeclaration(koinApplication)回到應(yīng)用自己的配置邏輯,并將這個koinApplication傳出去。下面就是對這個koinApplication的簡單配置說明(printLogger 和modules 都是koinApplication的成員方法)

    startKoin {
        printLogger()// 使用System.out.print來輸出日記
        modules(listOf(coffeeAppModule))// 加載modules
    }

(5)koinApplication.createEagerInstances(),應(yīng)用配置按照自己的需要配置好koinapplication,接著又回到框架的邏輯,創(chuàng)建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的時候就提前創(chuàng)建好對象,相當(dāng)于是預(yù)加載。到這里,koin的環(huán)境就完全搭建起來了,在需要注入對象的地方,可以直接通過koin的來獲取。

4.2 模塊加載

在上面KoinApplication的配置中說到,會調(diào)用koinApplition的modules來配置注入的模塊,現(xiàn)在來看看加載這些模塊的邏輯。

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>,然后會調(diào)用loadModuleskoin.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字段中,然后接著調(diào)用_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!")
            }
        }
    }

循環(huán)調(diào)用ScopeRegistry.loadModule

    private fun loadModule(module: Module) {
        declareScope(module.rootScope)
        declareScopes(module.otherScopes)
    }

每加載一個module,會將個module相關(guān)的的rootScope和otherScopes為參數(shù)調(diào)用到declareScope。module的rootScope和otherScopes都是ScopeDenifition類型的對象,經(jīng)過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>,如果已經(jīng)存在一個qualifer相同的scopeDenifition,則進行合并,復(fù)雜將新的scopeDenifition存起來。mergeDefinitions的邏輯相對復(fù)雜。

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

首先找到已經(jīng)存在的那個scopeDenifition。scopeDenifition的主要結(jié)構(gòu)是一個Set<BeanDenifition>,即這個scope中定義了哪些對象。合并的時候就是將傳入的scopeDefinition中定義的對象save到已經(jīng)存在的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)
    }

如果之前已經(jīng)存在一個相同的beanDefinition的話,需要新的beanDenition是不是強制override或者module是強制override.如果是的話,移除舊的,然后加入新的BeanDefinition,否則會報異常DefinitionOverrideException。因此override是一個比較重要的配置。我們回到前面,看看beanDefinition.options.overrideforceOverride 這兩個的值是如何確定的。

(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轉(zhuǎn)換成具體的生成對象的工廠,在注入對象的時候,將會使用這些工廠來生成具體的實例。

ScopeRegistry.declareInstances

     private fun declareInstances(scopeDefinition: ScopeDefinition) {
        _scopes.values.filter { it._scopeDefinition == scopeDefinition }.forEach { it.loadDefinitions(scopeDefinition) }
    }

_scopes是已經(jīng)存在的Scope對象,初識時,scopes為空的,因此,在開始loadModules時,并不會出現(xiàn)scope中的BeanDefinition轉(zhuǎn)換成工廠,但是如果已經(jīng)存在這樣的scope時,就會進入到Scope.loadDefinitions(scopeDefinition)

Scope.loadDefinitions:

    fun loadDefinitions(scopeDefinition: ScopeDefinition) {
            scopeDefinition.definitions.forEach {
             _instanceRegistry.createDefinition(it)
            }
     }

_instanceRegistry是Scope的一個InstanceRegistry類型的成員變量,這里遍歷scopeDefinition中定義的BeanDefinitions(definitions),調(diào)用_instanceRegistry.createDefinition來轉(zhuǎn)換成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)首先,根據(jù)single還是factory方法定義的生成不同的InstantaceFactory。

(2)將工廠關(guān)聯(lián)到主類

(3) 將工廠關(guān)聯(lián)到所有的次類

至此,module的加載邏輯全部結(jié)束。需要注意的是,BeanDefinition轉(zhuǎn)InstanceFactory的邏輯并不是每次都會執(zhí)行的。如果scope不存在的話,這部分邏輯是不執(zhí)行的。還有幾個疑問是,BeanDefinition的屬性比較多,比如primaryType和secondaryTypes 這兩個字段對BeanDefinition轉(zhuǎn)InstanceFactory都有影響,那他們是什么意思,如何配置的,這個也是比較重要的內(nèi)容。但不管怎么說,我們loadModule的主線邏輯我們已經(jīng)分析完畢。后面會再次補充細節(jié)一些的分支邏輯。

(2)在KoinApplication.loadModules結(jié)束后,接著做的是koin.createRootScope(),創(chuàng)建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,新創(chuàng)建的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
    }

調(diào)用Scope的構(gòu)造方法,然后將_rootScope作為links,調(diào)用scope的create方法

4.3 模塊卸載

加載的module也可以卸載,卸載時,會將這個module從加載的module集合中移除,同時會將這個module中聲明的所有的scope中的BeanDenifition,InstanceFactory都移除,并將module.isLoaded設(shè)置成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相關(guān)的BeanDefinition和InstanceFactory被清空了。

4.4 scope管理

關(guān)于Scope有兩個概念,一個是ScopeDefinition,是關(guān)于scope的定義,這個是在module里面通過scope函數(shù)來定義的;另一是Scope,是根據(jù)ScopeDefinition生成的真正的Scope對象,koin 使用ScopeRegistry來管理Scope 和 ScopeDefinition,其中ScopeDefinition時在loadModule的時候注冊的,在Module初始化時就會生成一個rootScope的ScopeDefinition,scope函數(shù)生成的會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結(jié)束后,會創(chuàng)建好rootScope,另外一個是手動調(diào)用koin.createScope等方法。在講createRootScope的時候,已經(jīng)講述了createScope函數(shù),這里就不在重復(fù)。這里主要講一下Scope本身屬性

  • scopeId,String的別名,Scope的ID
  • _source,Any,如果在scope中沒有找到對象,此時返回_soure
  • _linkedScope,關(guān)聯(lián)的其他scope,主要是rootScope
  • _instanceRegistry,解析好的對象工廠管理器

Scope包含了koin的主要業(yè)務(wù)邏輯,koin的很多接口也是委托到scope來實現(xiàn)。ScopeRegsitry是注冊Scope的組件。因此要創(chuàng)建或者獲取一個scope都是通過ScopeRegsitry來實現(xiàn)的,我們來看一下scope的獲取邏輯

Koin.getOrCreateScope:

    fun getOrCreateScope(scopeId: ScopeID, qualifier: Qualifier): Scope {
        return _scopeRegistry.getScopeOrNull(scopeId) ?: createScope(scopeId, qualifier)
    }

可以看到,標(biāo)志一個scope需要兩個維度,一個是scopeId,另外一個qualifier。首先以scopeId來查詢是否有存在的scope
ScopeRegister.getScopeOrNull:

    fun getScopeOrNull(scopeId: ScopeID): Scope? {
        return scopes[scopeId]
    }

在ScopeRegistry中用一個Map來存放已經(jīng)創(chuàng)建的scope,key為ScopeID。如果沒有找到,就創(chuàng)建

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}'")
        }
    }

這段邏輯比較簡單,如果已經(jīng)存在相同的scopeId了,則拋出異常ScopeAlreadyCreatedException。否則以qualifer去查找scopeDefinition,如果存在這樣的scopeDefinition,則用該scopeDefinition創(chuàng)建一個新的scope,然后記錄下載并當(dāng)作結(jié)果返回,否則報異常NoScopeDefFoundException,scopeId 對與scope本身來說是沒有意義的。從這段代碼我們可以看出,如果scopeId與第一個scope綁定后,如果這個scope刪除后,是可以綁定到另外一個qualifiered的scope,即一個scopeId是有可能對應(yīng)不同qualifer的scope的。使用時也需要注意,盡量不要出現(xiàn)這種情況。

scope也是可以被刪除的

Koin.deleteScope:

    fun deleteScope(scopeId: ScopeID) {
         _scopeRegistry.deleteScope(scopeId)
    }   

ScopeRegister.deleteScope:

    fun deleteScope(scopeId: ScopeID) {
        _scopes.remove(scopeId)
    }
4.6 Module 和Scope的關(guān)系
  1. scope由module來定義。也就是ScopeDefinition由module來生成
  2. scope對象一旦生成,就與module沒有太大關(guān)系,因為scope 和scopeDefintion對象生成后,就交給ScopeRegistry管理,scopeRegistry是koin的成員變量,因此可以不局限于定義的module內(nèi),是koin全局共享的
4.7 對象查找過程

對象的查找邏輯最終是在scope中實現(xiàn)

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

入?yún)⑷齻€:

  1. 請求對象的class,
  2. 對象的限定符qulifier,可選,默認為null
  3. 生成對象的參數(shù),可選,默認為null

然后會進入到resolveInstance函數(shù)

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

查找是由三個層級:

  1. 首先在當(dāng)前的scope中查找,如果沒有查找,則
  2. 在linkedScope中查找,默認是rootScope,如若還沒有找到,則
  3. 嘗試使用source對象,如果source的類型不一致則
  4. 拋出異常。

因此需要要注意的是,一個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里聲明了依賴的三個對象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。