ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Koin 注入参数(Injection Parameters)完全指南:定义、传递与解析

Koin 注入参数(Injection Parameters)完全指南:定义、传递与解析 Koin 注入参数Injection Parameters完全指南定义、传递与解析【免费下载链接】koinKoin - a pragmatic lightweight dependency injection framework for Kotlin Kotlin Multiplatform项目地址: https://gitcode.com/gh_mirrors/ko/koin本文基于 Koin 开源仓库 docs/reference/koin-core/injection-parameters.md 编写并辅以 koin-core 源码与测试验证。注入参数Injection Parameters是 Koin 在运行时向组件定义definition传递动态值的核心机制广泛应用于传参构建对象、ViewModel 实例传递、工厂按需创建等场景。读完本文你将掌握parametersOf/parameterArrayOf/parameterSetOf三种参数构建方式、按索引与按类型解析参数、级联传参以及参数与定义类型冲突时的规避方案。一、什么是注入参数在 Koin 中任何一个组件定义definition都可以接收注入参数injection parameters这些参数会被注入到定义中供该定义在构建实例时使用。与依赖图dependency graph中静态解析的依赖不同注入参数是由调用方在请求实例时动态传入的因此非常适合传递只有运行时才知道的值例如Activity / Fragment 中由 UI 上下文提供的值如View、Intent数据Compose Navigation 中上游ViewModel向下游ViewModel传递的实例每次调用get()时希望传入不同值的factory定义。从源码层面看Koin 中所有定义的实际类型是typealias DefinitionT Scope.(ParametersHolder) - T见 BeanDefinition.kt也就是说每个定义本质上都是一个接收ParametersHolder参数持有者的函数注入参数正是通过这个ParametersHolder传入定义并完成解析的。二、向定义传递参数parametersOf假设我们有如下定义它依赖两个值a、b来构造Presenterclass Presenter(val a : A, val b : B) val myModule module { single { params - Presenter(a params.get(), b params.get()) } }在定义内部params就是ParametersHolder通过params.getT()可以按类型取出传入的参数。参数通过parametersOf()函数发送给定义每个值用逗号分隔class MyComponent : View, KoinComponent { val a : A ... val b : B ... // inject this as View value val presenter : Presenter by inject { parametersOf(a, b) } }在仓库测试 ParametersInjectionTest.kt 中有对应的完整验证val app koinApplication { modules( module { single { params - Simple.MySingle(params.get()) } }, ) } val koin app.koin val a: Simple.MySingle koin.get { parametersOf(42) } assertEquals(42, a.id)除了by inject { parametersOf(...) }用于KoinComponent或属性委托你同样可以在任意位置通过koin.getT { parametersOf(...) }或scope.getT { parametersOf(...) }显式携带参数请求实例。三、在定义中声明注入参数3.1 使用 params.get() 获取参数假如我们需要一个view参数来构建Presenter可以使用定义函数的params参数来检索注入参数class Presenter(val view : View) val myModule module { single { params - Presenter(view params.get()) } }3.2 使用解构声明直接书写参数Koin 也支持把注入参数直接写进定义的 lambda 参数位置作为解构声明destructured declarationclass Presenter(val view : View) val myModule module { single { (view : View) - Presenter(view) } }从源码看ParametersHolder实现了component1()~component5()操作符见 ParametersHolder.kt解构声明正是通过它们按位置0 到 4取出参数inline operator fun reified T component1(): T elementAt(0, T::class) inline operator fun reified T component2(): T elementAt(1, T::class) // ... component3 / component4 / component5仓库测试 ParametersInjectionTest.kt 中can create a single with parameters in order - destruct即验证了解构方式module { single { (a: Int, b: Int) - Simple.MyTwinSingle(a, b) } } val a: Simple.MyTwinSingle koin.get { parametersOf(42, 24) } assertEquals(42, a.i1) assertEquals(24, a.i2)⚠️ 注意官方文档警示即便解构声明写法更简洁、可读性更好但它不是类型安全的。当存在多个参数时Kotlin 无法检测你传入的类型顺序是否正确。例如(a: Int, b: String)解构时如果调用方按parametersOf(x, 42)传入Kotlin 编译器不会报错运行时才会出问题。因此在参数较多、类型容易混淆的场景下优先使用params.getT()按类型解析。四、按索引解析注入参数get(index) 与 [ ]如果不使用get()按类型解析而同一类型存在多个参数你可以用索引来解析get(index)等价于[ ]操作符class Presenter(val view : View) val myModule module { single { p - Presenter(p[0],p[1]) } }ParametersHolder源码中索引访问的实现ParametersHolder.ktoperator fun T get(i: Int) _values[i] as T测试 ParametersInjectionTest.kt 中can create a single with parameters in order验证了这一写法module { single { p - Simple.MyTwinSingle(p[0], p[1]) } } val a: Simple.MyTwinSingle koin.get { parametersOf(42, 24) } assertEquals(42, a.i1) assertEquals(24, a.i2)注意get(index)越界时elementAt会抛出NoParameterFoundException提示无法从参数持有者中取到第 N 个参数见 ParametersHolder.kt。五、从依赖图中解析注入参数直接 get()Koin 的图解析所有定义的解析主树也允许你直接解析注入参数——只需在定义中使用常规的get()函数class Presenter(val view : View) val myModule module { single { Presenter(get()) } }此时get()会先从注入参数栈中查找匹配类型的值找到则直接使用找不到再回退到依赖图registry解析。仓库测试can create a single with parameters - using graph resolutionParametersInjectionTest.kt证明koin.get { parametersOf(42) }时定义内get()拿到的是注入的42而非图中的其他定义。更复杂的场景——多个定义同时使用图解析并配合named限定符module { single { Simple.MySingle(get()) } single(named(2)) { Simple.MySingle(get()) } } assertEquals(42, koin.getSimple.MySingle { parametersOf(42) }.id) assertEquals(24, koin.getSimple.MySingle(named(2)) { parametersOf(24) }.id)六、三种参数构建方式parametersOf / parameterArrayOf / parameterSetOf在parametersOf之外Koin自 3.4.3 起还提供了两种专用参数构建 API。三者都返回ParametersHolder区别在于内部消费方式对应源码 ParametersHolder.kt构建函数底层useIndexedValues消费方式适用场景parametersOf(vararg)null索引优先回退类型先按索引消费失败则按类型匹配首个值通用场景两种解析兼得parameterArrayOf(vararg)true严格按索引依次消费级联传参、多个同类型参数parameterSetOf(vararg)false严格按类型匹配不使用索引传递不同类型值的集合6.1 parameterArrayOf按索引消费的数组parameterArrayOf用于传一个数组值数据按索引被消费val params parameterArrayOf(1,2,3) params.getInt() 1 params.getInt() 2 params.getInt() 3 params.getInt() 3注意最后一行当索引到达最后一个元素后increaseIndex()不会继续自增见 ParametersHolder.kt因此重复调用getInt()会停留在3。测试 CascadeParamTest.kt 中的parameter_array精确验证了这一行为含索引游标变化val p parameterArrayOf(intParam, stringParam) assertEquals(0, p.index) assertNull(p.getOrNullString()) // 索引0是 Int类型不匹配 → null索引不动 assertEquals(intParam, p.getInt()) // 索引0匹配 → 消费并自增 assertEquals(stringParam, p.getString()) // 索引1匹配 → 消费 assertEquals(1, p.index)6.2 parameterSetOf按类型匹配的集合parameterSetOf用于传一组不同类型的值不使用索引滚动值而是每次按请求的类型匹配val params parameterSetOf(a_string, 42) params.getInt() 42 params.getString() a_string params.getInt() 42 params.getString() a_string测试parameter_setCascadeParamTest.kt验证无论请求多少次、按什么顺序parameterSetOf都通过getFirstValue按类型找到匹配值索引始终保持为 0。6.3 parametersOf索引与类型的混合模式默认的parametersOf同时兼容索引与类型两种解析当useIndexedValues null时getOrNull(clazz)会先尝试取当前索引处的值类型需匹配失败则回退到按类型匹配首个值源码 ParametersHolder.ktwhen (useIndexedValues) { null - getIndexedValueT(clazz) ?: getFirstValueT(clazz) true - getIndexedValueT(clazz) else - getFirstValueT(clazz) }因此文档中的示例成立val params parametersOf(1,2,a_string) params.getString() a_string params.getInt() 1 params.getInt() 2 params.getInt() 2 params.getString() a_string其消费过程可以这样理解请求String时索引 0 处是Int不匹配回退按类型找到a_string请求Int时索引 0 匹配1消费并推进索引到 1再次请求Int命中2再请求Int时索引停在2最后一个元素最后请求String回退按类型找到a_string。仓库测试consume_bad_valueCascadeParamTest.kt正是对这一混合行为的回归验证。6.4 选择建议官方文档提示你可以用parametersOf或parameterArrayOf级联cascade参数注入即基于索引依次消费值也可以用parametersOf或parameterSetOf基于类型级联解析。选择哪种取决于你的传参结构是位置敏感还是类型敏感。七、参数级联Cascade多级定义共享同一组参数级联注入是注入参数最强大的应用之一一次传入的一组参数可以沿着依赖链被多个定义依次消费。例如 CascadeParamTest.kt 中can_cascade_param_full_ctor_dslfactoryOf(Simple::MyIntFactory) factoryOf(Simple::MyStringFactory) factoryOf(Simple::AllFactory) val allFactory koin.getSimple.AllFactory { parameterArrayOf(intParam, stringParam) } assertEquals(intParam, allFactory.ints.id) assertEquals(stringParam, allFactory.strings.s)AllFactory依赖MyIntFactory消费Int参数和MyStringFactory消费String参数parameterArrayOf传入的(42, _string_)被依次级联消费。同样地ParametersInjectionTest.kt 中chained factory injection展示了对子定义显式重传参数的方式factory { (i: Int) - Simple.MyIntFactory(i) } factory { (s: String) - Simple.MyStringFactory(s) } factory { (i: Int, s: String) - Simple.AllFactory( get { parametersOf(i) }, get { parametersOf(s) }, ) }如果子定义本身需要携带不同参数可以在get { parametersOf(...) }中重新指定如果子定义没有特殊参数需求则直接使用图解析get()参数会自动级联。八、可空参数与安全解析get / getOrNull / getOrNull()8.1 可空参数注入参数本身可以是null。测试can create a single with nullable parameters验证single { (i: Int?) - Simple.MySingleWithNull(i) } val a: Simple.MySingleWithNull koin.get { parametersOf(null) } assertEquals(null, a.id)8.2 getOrNull 安全解析当参数可能缺失时使用getOrNull()避免异常。测试nullable_injection_param与inject_param_get_or_nullParametersInjectionTest.ktsingle { p - Simple.MySingleWithNull(p.getOrNull()) } val a: Simple.MySingleWithNull koin.get() // 不传参数也不抛异常 assertNull(a.id)注意getT()在找不到匹配类型时会抛出DefinitionParameterExceptionNo value found for type ...而getOrNull()返回null源码见 ParametersHolder.kt。另外getOrNull也支持传KClass如p.getOrNullString(String::class)。8.3 类型匹配的细节getFirstValue使用clazz.isInstance(it)判断ParametersHolder.kt因此父接口类型可以匹配到子类实例。测试assignable type values验证了这一点val p parametersOf(Simple.Component1()) assertNotNull(p.getSimple.ComponentInterface1())九、参数与定义类型冲突必须用包装类型隔离⚠️ 官方文档警告如果通过parametersOf传入的某个值与所请求定义具有相同类型Koin 会直接返回该值本身并跳过 factory 构建块。为避免这种冲突参数应使用包装类型如 value class隔离。这一点在仓库测试injected parameter instance should be used directly - not resolved from registryParametersInjectionTest.kt中有明确的回归验证对应 issue #2337// SharedViewModel 有定义且需要 Int 参数 single { (id: Int) - SharedViewModel(id) } factory { ConsumerViewModel(get()) } // 传入已存在的 SharedViewModel 实例应直接使用该实例而不是从注册表重建 val consumer: ConsumerViewModel koin.get { parametersOf(existingSharedVM) } assertEquals(existingSharedVM, consumer.shared)也就是说当参数类型与定义返回类型相同例如向依赖SharedViewModel的ConsumerViewModel传入SharedViewModel实例时Koin 会短路——直接使用传入实例跳过 factory 逻辑。这是注入参数优先于注册表解析的有意设计但反过来也意味着如果你希望参数与定义返回类型相同却又想让 factory 块真正执行就必须引入包装类型JvmInline value class ViewModelParam(val viewModel: SharedViewModel) single { (p: ViewModelParam) - ConsumerViewModel(p.viewModel) }顺带一提仓库 QualifierParameterShadowingTest.kt 记录了一个已知边界行为参数栈按类型忽略限定符优先匹配因此与parametersOf传入值同类型的get(named(...))依赖可能被参数遮蔽——在设计 API 时应注意避免限定符依赖与传入参数类型重叠。十、ParametersHolder 的更多实用操作ParametersHolder除了解析还支持运行时修改参数集合源码 ParametersHolder.ktAPI说明size()/isEmpty()/isNotEmpty()参数数量与空判断get(i)/set(i, t)按索引读写set支持替换值insert(index, value)在指定位置插入参数返回自身add(value)追加参数返回自身getT()/getOrNullT()/getOrNull(clazz)按类型解析见上文component1()~component5()供解构声明使用测试 ParametersHolderTest.kt 对这些操作均有覆盖例如val params parametersOf(empty, 42) val newParams params.insert(0, myInt) assertEquals(3, newParams.size()) assertEquals(newParams.getInt(0), myInt)十一、single 与 factory 的参数语义差异注入参数与定义的生命周期语义需要区分清楚single单例参数只在首次创建实例时生效。测试can get a single created with parameters - no need of give it again证明首次koin.get { parametersOf(42) }创建后后续koin.get()不带参数也能拿到同一实例id 仍为 42。factory工厂每次get()都会用传入的参数重新创建新实例。测试can create factories with params验证get { parametersOf(42) }与get { parametersOf(43) }会得到两个不同 id 的实例。因此动态性要求高的场景如每次请求不同配置应使用factory而固定的运行时参数交给single首次注入即可。十二、综合实战示例结合以上全部能力一个完整的多参数、多模式注入示例// 1. 按类型解析 图解析混用 single { params - Presenter(view params.get(), repo get()) } // 2. 多个同类型参数按索引解析 single { p - TwinPresenter(p[0], p[1]) } // 3. 解构声明注意类型顺序风险 single { (view: View, id: Long) - ScopedPresenter(view, id) } // 4. 请求时传参KoinComponent 委托注入 class MyScreen : KoinComponent { val presenter: Presenter by inject { parametersOf(view, repository) } } // 5. 直接调用传参级联给子定义 val presenter: Presenter koin.get { parameterArrayOf(view, repository) } // 6. 类型集合传参 val presenter: Presenter koin.get { parameterSetOf(view, repository, 42L) }对应模块组装val app koinApplication { modules( module { single { params - Presenter(view params.get(), repo get()) } single { p - TwinPresenter(p[0], p[1]) } factory { (view: View, id: Long) - ScopedPresenter(view, id) } }, ) }十三、小结注入参数是 Koin 面向运行时动态传参的核心 API其要点可归纳为传递用parametersOf()在inject {}/get {}中传参声明定义中可用params.get()类型安全或解构声明简洁但需注意类型顺序解析get()按类型、get(index)/p[i]按索引二者可由get()混用三种构建器parametersOf索引类型混合、parameterArrayOf纯索引、parameterSetOf纯类型级联一组参数可沿依赖链被多个定义消费冲突规避参数与定义返回类型相同时用 value class 等包装类型隔离语义差异single参数只在首次创建生效factory每次重建。如需深入验证可继续阅读仓库中的 ParametersHolder.kt参数持有者实现、ParametersInjectionTest.kt 与 CascadeParamTest.kt行为验证以及 definitions.md定义 DSL 总览与 injection.md注入方式概览。【免费下载链接】koinKoin - a pragmatic lightweight dependency injection framework for Kotlin Kotlin Multiplatform项目地址: https://gitcode.com/gh_mirrors/ko/koin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表