我正在使用Koin 3.2,它具有新的模块包含功能。在官方文档中,在讨论模块链接策略时,有这样一段话:
需要注意的一个重要细节是,您也可以使用 include 来添加内部和私有模块 - 这使您可以灵活地在模块化项目中公开哪些内容。
这正是我所需要的,但我在文档的其他地方找不到如何设置仅为父模块提供依赖项的“私有”模块,以便这些子依赖项不可用于注入。例如:
class SomeNonInjectableClass
class SomeInjectableClass(private val sni : SomeNonInjectableClass)
val privateModule = module {
singleOf(::SomeNonInjectableClass)
}
val publicModule = module {
includes(privateModule)
singleOf(::SomeInjectableClass)
}
在我的主应用程序中,我仅列出公共模块,但 Koin 自动提供所有包含的模块:
startKoin{
androidLogger()
androidContext(this@Main)
modules(publicModule)
}
现在开发人员可以从任何活动中执行此操作:
val foo : SomeInjectableClass by inject() //Ok
val bar : SomeNonInjectableClass by inject() //I don't want this
我希望开发人员无法从私有模块注入不可注入的类。类似于 Dagger 2 的
@NonInjectable
标记限定符。
这可能吗?还是我应该使用经典 DSL 手动构建我的定义?
据我了解,这个新的
includes
功能只是使整个模块定义 private
或 internal
成为可能,不允许您将它们包含到此范围之外的模块中。我想它更多地与控制大型模块化项目中 Koin 模块的创建有关,而不是能够控制开发人员可以或不能注入什么。
private val privateModule = module {
singleOf(::SomeNonInjectableClass)
}
我可以理解,在跨不同 kotlin 模块创建大型 koin 模块组合时,如何将其设为私有范围可以避免一些混乱(特别是私有模块内有很多定义),但同时我也感到沮丧似乎不可能实现你想做的事情。
澄清一下:
includes
运算符允许使用给定模块加载其他模块。
如果您想避免共享内部实现,请检查您的 Gradle 模块暴露情况。例如,共享接口即可。
我试图做同样的事情,并找到了一些答案。
import org.koin.core.context.startKoin
import org.koin.core.qualifier.named
import org.koin.dsl.module
val module = module {
scope(named("private")) {
scoped(named("PRIVATE_KEY")) { "ABCD1234" }
}
single(named("result")) {
val scope = getKoin().getOrCreateScope("private", named("private"))
"Key is ${scope.get<String>(named("PRIVATE_KEY"))}"
}
}
fun main() {
val koinApp = startKoin {
modules(module)
}
val koin = koinApp.koin
val result = koin.get<String>(named("result"))
println("Result: $result") // -> Result: Key is ABCD1234
val keyVisible = koin.getOrNull<String>(named("PRIVATE_KEY")) != null
println("Key visibility: $keyVisible") // -> Key visibility: false
}
您可以通过将依赖项移动到不同的范围来隐藏依赖项。 然而,这是默认的,因为
koinApp.get()
正在尝试从根范围获取值。
同样,您必须在外部模块内部定义内部依赖模块。
这是我发现的更好的方法。
val privateModule = module {
single(named("1")) { 10 }
single(named("2")) { 20 }
}
val publicModule = module {
single(named("result")) {
val a = privateModule.get<Int>(named("1"))
val b = privateModule.get<Int>(named("2"))
a + b
}
}
context(Scope)
@OptIn(KoinInternalApi::class)
inline fun <reified T> Module.get(
qualifier: Qualifier,
noinline parameters: ParametersDefinition? = null,
): T {
val scope = this@Scope
val instanceFactory =
mappings[indexKey(T::class, qualifier, named("_root_"))] ?: error("No definition found from $qualifier")
val context = InstanceContext(scope.logger, scope, parameters?.invoke())
return instanceFactory.get(context) as T
}
fun main() {
val koinApp = startKoin {
modules(publicModule)
}
val koin = koinApp.koin
val result = koin.get<Int>(named("result"))
println("Result: $result") // -> Result: 30
val internalVisible = koin.getOrNull<Int>(named("1")) != null
println("Internal visibility: $internalVisible") // Internal visibility: false
}
它使用 Kotlin 的实验性 Context 功能来实现美观的代码。 如果您不想使用上下文,可以使用类似
use(privateModule).get()
的语法。
解决方案选择内部使用 Koin API 并对根范围进行硬编码,但它还算有效。 不使用作用域意味着不可能从外部解决私有依赖关系。您只需将私有模块定义为私有 val 即可。
希望这能帮助那些想做同样事情的人!