Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explicit bean override functionality #123

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import org.koin.standalone.StandAloneContext
*/
inline fun <reified T : ViewModel> Context.viewModel(
name: String = "",
override: Boolean = false,
noinline definition: Definition<T>
) {
val bean = factory(name, definition)
val bean = factory(name, override, definition)
bean.bind(ViewModel::class)
}

Expand Down Expand Up @@ -72,4 +73,4 @@ fun <T> KoinComponent.get(modelClass: Class<T>, parameters: Parameters): T =
* Resolve an instance by its canonical name
*/
fun <T> KoinComponent.getByName(name: String, parameters: Parameters): T =
(StandAloneContext.koinContext as KoinContext).getByName(name, parameters)
(StandAloneContext.koinContext as KoinContext).getByName(name, parameters)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data class BeanDefinition<out T>(
var types: List<KClass<*>> = arrayListOf(),
val scope: Scope = Scope.root(),
val isSingleton: Boolean = true,
val override: Boolean = false,
val definition: Definition<T>
) {

Expand All @@ -42,15 +43,16 @@ data class BeanDefinition<out T>(
*/
fun isNotASingleton() = !isSingleton

private fun boundTypes(): String = "(" + types.map { it.java.canonicalName }.joinToString() + ")"
private fun boundTypes(): String = "(" + types.joinToString { it.java.canonicalName } + ")"

override fun toString(): String {
val n = if (name.isBlank()) "" else "name='$name', "
val c = "class=${clazz.java.canonicalName}"
val s = if (isSingleton) "Bean" else "Factory"
val b = if (types.isEmpty()) "" else ", binds~${boundTypes()}"
val sn = if (scope != Scope.root()) ", scope:${scope.name}" else ""
return "$s[$n$c$b$sn]"
val o = ", override=$override"
return "$s[$n$c$b$sn$o]"
}

override fun equals(other: Any?): Boolean {
Expand All @@ -64,4 +66,4 @@ data class BeanDefinition<out T>(
}
}

typealias Definition<T> = (ParameterProvider) -> T
typealias Definition<T> = (ParameterProvider) -> T
12 changes: 8 additions & 4 deletions koin-core/src/main/kotlin/org/koin/core/bean/BeanRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.koin.core.bean

import org.koin.Koin
import org.koin.core.scope.Scope
import org.koin.error.BeanOverrideException
import org.koin.error.NoScopeFoundException
import java.util.*

Expand Down Expand Up @@ -30,6 +31,9 @@ class BeanRegistry {
val definition = def.copy(scope = scope)
val existingBean = definitions.firstOrNull { it == definition }
val override = existingBean != null
if (override && !definition.override) {
throw BeanOverrideException("$definition would override $existingBean but override flag is false")
}
existingBean?.let {
definitions.remove(existingBean)
}
Expand Down Expand Up @@ -88,17 +92,17 @@ class BeanRegistry {
* Get bean definitions from given scope context & child
*/
fun getDefinitionsFromScope(name: String): List<BeanDefinition<*>> {
val scopes = allScopesfrom(name).toSet()
val scopes = allScopesFrom(name).toSet()
return definitions.filter { def -> definitions.first { it == def }.scope in scopes }
}

/**
* Retrieve scope and child for given name
*/
private fun allScopesfrom(name: String): List<Scope> {
private fun allScopesFrom(name: String): List<Scope> {
val scope = getScope(name)
val firstChild = scopes.filter { it.parent == scope }
return listOf(scope) + firstChild + firstChild.flatMap { allScopesfrom(it.name) }
return listOf(scope) + firstChild + firstChild.flatMap { allScopesFrom(it.name) }
}

/**
Expand All @@ -109,4 +113,4 @@ class BeanRegistry {
scopes.clear()
}

}
}
24 changes: 15 additions & 9 deletions koin-core/src/main/kotlin/org/koin/dsl/context/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import org.koin.core.scope.Scope
*
* @author - Arnaud GIULIANI
*/
class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {
class Context(val name: String = Scope.ROOT, val koinContext: KoinContext, val override: Boolean) {

/**
* bean definitions
Expand All @@ -27,9 +27,10 @@ class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {
/**
* Create a sub context in actual context
* @param name
* @param override
*/
fun context(name: String, init: Context.() -> Unit): Context {
val newContext = Context(name, koinContext)
fun context(name: String, override: Boolean? = null, init: Context.() -> Unit): Context {
val newContext = Context(name, koinContext, override ?: this@Context.override)
subContexts += newContext
return newContext.apply(init)
}
Expand All @@ -43,9 +44,14 @@ class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {
inline fun <reified T : Any> provide(
name: String = "",
isSingleton: Boolean = true,
override: Boolean? = null,
noinline definition: Definition<T>
): BeanDefinition<T> {
val beanDefinition = BeanDefinition(name, T::class, isSingleton = isSingleton, definition = definition)
val beanDefinition = BeanDefinition(
name, T::class,
isSingleton = isSingleton,
override = override ?: this@Context.override,
definition = definition)
definitions += beanDefinition
return beanDefinition
}
Expand All @@ -54,8 +60,8 @@ class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {
* Provide a bean definition - alias to provide
* @param name
*/
inline fun <reified T : Any> bean(name: String = "", noinline definition: Definition<T>): BeanDefinition<T> {
return provide(name, true, definition)
inline fun <reified T : Any> bean(name: String = "", override: Boolean? = null, noinline definition: Definition<T>): BeanDefinition<T> {
return provide(name, true, override, definition)
}

/**
Expand All @@ -64,8 +70,8 @@ class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {
*
* @param name
*/
inline fun <reified T : Any> factory(name: String = "", noinline definition: Definition<T>): BeanDefinition<T> {
return provide(name, false, definition)
inline fun <reified T : Any> factory(name: String = "", override: Boolean? = null, noinline definition: Definition<T>): BeanDefinition<T> {
return provide(name, false, override, definition)
}

/**
Expand Down Expand Up @@ -100,4 +106,4 @@ class Context(val name: String = Scope.ROOT, val koinContext: KoinContext) {

// String display
override fun toString(): String = "Context[$name]"
}
}
5 changes: 3 additions & 2 deletions koin-core/src/main/kotlin/org/koin/dsl/module/Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import org.koin.standalone.StandAloneContext
/**
* Create Context
*/
fun applicationContext(init: Context.() -> Unit): Module = { Context(Scope.ROOT, StandAloneContext.koinContext as KoinContext).apply(init) }
fun applicationContext(override: Boolean = false,
init: Context.() -> Unit): Module = { Context(Scope.ROOT, StandAloneContext.koinContext as KoinContext, override).apply(init) }

/**
* Module - function that gives a module
*/
typealias Module = () -> Context
typealias Module = () -> Context
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.koin.error

class BeanOverrideException(msg: String) : Exception(msg)
6 changes: 3 additions & 3 deletions koin-spark/src/main/kotlin/org/koin/spark/KoinExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import org.koin.dsl.context.ParameterHolder
/**
* Declare a Spark controller
*/
inline fun <reified T : Any> Context.controller(name: String = "", noinline definition: Definition<T>) {
val def = bean(name, definition)
inline fun <reified T : Any> Context.controller(name: String = "", override: Boolean = false, noinline definition: Definition<T>) {
val def = bean(name, override, definition)
def.bind(SparkController::class)
}

Expand Down Expand Up @@ -38,4 +38,4 @@ fun KoinContext.runSparkControllers() {
Koin.logger.log("Creating $def ...")
instanceFactory.retrieveInstance<Any>(def, ParameterHolder())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import org.koin.test.AutoCloseKoinTest

class GenericBindingTest : AutoCloseKoinTest() {

val module = applicationContext {
val module = applicationContext(override = true) {
bean("a") { ComponentA() as InterfaceComponent<String> }
bean("b") { ComponentB() as InterfaceComponent<Int> }

}

val badModule = applicationContext {
val badModule = applicationContext(override = true) {
bean { ComponentA() as InterfaceComponent<String> }
bean { ComponentB() as InterfaceComponent<Int> }

Expand All @@ -43,4 +43,4 @@ class GenericBindingTest : AutoCloseKoinTest() {
// Bean has been overridden
assertFalse(a is ComponentA)
}
}
}
88 changes: 83 additions & 5 deletions koin-test/src/test/kotlin/org/koin/test/core/OverrideTest.kt
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
package org.koin.test.core

import org.junit.Assert.*
import org.junit.Ignore
import org.junit.Test
import org.koin.dsl.module.applicationContext
import org.koin.error.BeanOverrideException
import org.koin.standalone.StandAloneContext.loadKoinModules
import org.koin.standalone.StandAloneContext.startKoin
import org.koin.standalone.get
import org.koin.standalone.releaseContext
import org.koin.test.AutoCloseKoinTest

class OverrideTest : AutoCloseKoinTest() {

val sampleModule1 = applicationContext {
val sampleModule1 = applicationContext(override = true) {
bean { ComponentA() } bind MyInterface::class
bean { ComponentA() }
}

val sampleModule2 = applicationContext {
val sampleModule2 = applicationContext(override = true) {
bean("A") { ComponentA() as MyInterface }
bean("B") { ComponentB() as MyInterface }
}

val sampleModule3 = applicationContext {
val sampleModule3 = applicationContext(override = true) {
bean { ComponentB() as MyInterface }
bean { ComponentA() as MyInterface }
}

val sampleModule4 = applicationContext {
val sampleModule4 = applicationContext(override = true) {
bean { ComponentB() as MyInterface }
factory { ComponentA() as MyInterface }
}

val noOverrideModule = applicationContext {
bean { ComponentA() as MyInterface }
bean { ComponentB() as MyInterface }
}

val overrideOnBeanDefinitionModule = applicationContext {
bean { ComponentA() as MyInterface }
bean(override = true) { ComponentB() as MyInterface }
}

val overrideOnSubContextModule = applicationContext {
context(name = "SUBCONTEXT", override = true) {
bean { ComponentA() as MyInterface }
bean { ComponentB() as MyInterface }
}
}

val noOverrideWithSubContextModule = applicationContext {
context(name = "SUBCONTEXT") {
bean { ComponentA() as MyInterface }
}
}

class ComponentA : MyInterface
class ComponentB : MyInterface
interface MyInterface
Expand Down Expand Up @@ -72,4 +99,55 @@ class OverrideTest : AutoCloseKoinTest() {
assertTrue(intf is ComponentA)
assertNotEquals(intf, get<MyInterface>())
}
}

@Test
fun `override without override flag`() {
try {
startKoin(listOf(noOverrideModule))
fail("Should throw BeanOverrideException")
} catch (e: BeanOverrideException) {
}
}

@Test
fun `override on bean definition`() {
startKoin(listOf(overrideOnBeanDefinitionModule))

val intf = get<MyInterface>()
assertNotNull(intf)
assertTrue(intf is ComponentB)
}

@Test
fun `override on sub context`() {
startKoin(listOf(overrideOnSubContextModule))

val intf = get<MyInterface>()
assertNotNull(intf)
assertTrue(intf is ComponentB)
}

@Test
@Ignore
fun `no override with sub context`() {
startKoin(listOf(noOverrideWithSubContextModule))

val intf = get<MyInterface>()
assertNotNull(intf)
assertTrue(intf is ComponentA)

try {
loadKoinModules(noOverrideWithSubContextModule)
fail("Should throw BeanOverrideException")
} catch (e: BeanOverrideException) {
}

releaseContext("SUBCONTEXT")

try {
loadKoinModules(noOverrideWithSubContextModule)
} catch (e: BeanOverrideException) {
fail("Should NOT throw BeanOverrideException because context was released")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ class RunModulesTest : AutoCloseKoinTest() {
bean { ComponentB(get()) }
}

val moduleC = applicationContext {
val moduleC = applicationContext(override = true) {
bean { ComponentA("A2") }
}

val moduleD = applicationContext {
context("D") {
context("D", override = true) {
bean { ComponentA("D") }
}
}
Expand Down Expand Up @@ -88,4 +88,4 @@ class RunModulesTest : AutoCloseKoinTest() {
assertDefinitions(1)
assertContexts(2)
}
}
}