探索Koin框架:简单、灵活的依赖注入解决方案
总览
koin框架是一个纯Kotlin实现,使用Kotlin DSL去描述对象注入和对象间依赖关系以及创建规则。站在使用方的角度,我们需要理解Koin下面的几个组成部分:
Koin:Koin框架的使用入口,Koin自身核心功能的实现处Definition:定义,指的是我们写在dsl里面的对象关系、创建规则的相关定义Scope:对象生命周期的管理者Factory:创建对象的工厂,包括了多种方式,例如单例、每次生成新对象、生命周期内内生成唯一对象inject:注入对象,获取注入的对象
这些不同的组成部分我绘制成一个示意图,可以在一段时间后快速帮助自己回忆起Koin的组成部分。(讲道理一段时间后还能让使用者记住他是“如何使用,每个模块的分工以及如何串起来”的依赖注入框架,我还没遇到过)
使用
使用 startKoin 进行初始化:
startKoin {
}
闭包里面可以指定modules的定义,定义modules的时候还可以定义scope、factory:
scope(named("myscope")) {
scoped {
// 对象创建
...
}
factory {
// 对象创建,每次新创建一个,但是可以随着scope销毁
}
}
factory {
// 对象创建,每次新创建一个
...
}
}
然后我们在需要使用对象的地方去声明对象并注入对象:
val objWithScope by getKoin().getOrCreatedScope("",named("")).inject()
当对象依赖其他对象的时候,也可以通过get()去获取他的依赖性:
// 参数调用get()
factory {
Demo(get()) // inject内部也调用的是get()
}
原理分析
为了更清晰的理解Koin的运作方式,我们大概来看下他几个关键模块的实现
初始化
startKoin的时候,GlobalContext会创建并初始化我们的KoinApplication对象,KoinApplication是实际的工作对象Koin的包装,负责初始化Koin对象、加载modules等。
加载modules
最终会调用 Koin 的 loadModules 方法:
这里最重要的就是两件事:
- InstanceRegistry加载Module
- ScopeRegistry加载Scope
loadModule
loadModule就是把给存放InstanceFactory的Map从Module读取到到InstanceRegistry里面:
那么Module里面的mapping是如何生成的呢?往回追溯,是我们在factory、scoped等方法的时候确定的,他们会通过indexPrimaryType去往mapping写入内容:
mapping的key其实就是一个字符串索引,这个索引通过InstanceFactory的type+索引名+scope索引名来确定喂一性。
factory:
scoped:
loadScopes
这个比loadModules更简单,就是把Module里面声明的scope读出来,存到ScopeRegistry里面去,这里也能看出来 InstanceRegistry和ScopeRegistry符合单一职责原则,各管各的。
到这一步,对于Koin对象来说,我声明的对象依赖和生命周期都被你读取过去了,那通过Koin帮我创建对象也就不是什么难事了。
inject-创建对象
当我们用 get
、inject
去注入对象的时候,Koin会根据module+scope的信息帮我们创建我们需要的对象。inject、get在内部也都是调用的get:
七七八八经过几部调用,会调用InstanceRegistry的resolveInstance:
这时候就会从 _instances 里面取出相对于的 InstanceFactory 去创建对象:
至于这个内层的get方法,其实是Scope对象的方法。所以会结合特定的域的生命周期去创建对象。那外层直接调用 inject
的时候是调用的哪个 Scope 对象呢,那当然是 rootScope 的了,ScopeRegistry默认提供的一个Scope:
而InstanceFactory从最前面的模块图可以看出来,他有3个子类:
SingleInstanceFactory: 创建的是单例ScopedInstanceFactory: 根据域创建,同一个域复用对象FactoryInstanceFactory:创建行为和父类默认一直,每次都创建新对象
总结
看到这里相信Koin你也基本掌握基础用法了。Koin的用法还不止这些基础用法,包括Jetpack组件注入的封装,包括Kotlin跨平台的支持。总得来看Koin还是有下面一些优势:
简单简洁易用,快速学习,容易上手轻量,运行时,不影响编译速度,不会像dagger或者hilt一样生成一堆代码支持和Kotlin跨平台一起使用,潜力无限