Browse Source
As a follow-up of the ApplicationContext Kotlin extensions, close to
the Kotlin functional WebFlux DSL and partially inspired of the
Groovy/Scala bean configuration DSL, this commit introduces a
lightweight Kotlin DSL for functional bean declaration.
It allows declaring beans as following:
beans {
bean<Foo>()
profile("bar") {
bean<Bar>("bar", scope = Scope.PROTOTYPE)
}
environment({ it.activeProfiles.contains("baz") }) {
bean { Baz(it.ref()) }
bean { Baz(it.ref("bar")) }
}
}
Advantages compared to Regular ApplicationContext API are:
- No exposure of low-level ApplicationContext API
- Focused DSL easier to read, but also easier to write with a fewer
entries in the auto-complete
- Declarative syntax instead of functions with verbs like registerBeans
while still allowing programmatic registration of beans if needed
- Such DSL is idiomatic in Kotlin
- No need to have an ApplicationContext instance to write how you
register your beans since beans { } DSL is conceptually a
Consumer<GenericApplicationContext>
This DSL effectively replaces ApplicationContext Kotlin extensions as
the recommended way to register beans in a functional way with Kotlin.
Issue: SPR-15755
pull/1473/merge
2 changed files with 248 additions and 0 deletions
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
/* |
||||
* Copyright 2002-2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.context.support |
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinitionCustomizer |
||||
import org.springframework.context.ApplicationContext |
||||
import org.springframework.core.env.ConfigurableEnvironment |
||||
import java.util.function.Supplier |
||||
|
||||
/** |
||||
* Class implementing functional bean definition Kotlin DSL. |
||||
* |
||||
* @author Sebastien Deleuze |
||||
* @since 5.0 |
||||
*/ |
||||
open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean = { true }) : (GenericApplicationContext) -> Unit { |
||||
|
||||
protected val registrations = arrayListOf<(GenericApplicationContext) -> Unit>() |
||||
|
||||
protected val children = arrayListOf<BeanDefinitionDsl>() |
||||
|
||||
enum class Scope { |
||||
SINGLETON, |
||||
PROTOTYPE |
||||
} |
||||
|
||||
class BeanDefinitionContext(val context: ApplicationContext) { |
||||
|
||||
inline fun <reified T : Any> ref(name: String? = null) : T = when (name) { |
||||
null -> context.getBean(T::class.java) |
||||
else -> context.getBean(name, T::class.java) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Declare a bean definition from the given bean class which can be inferred when possible. |
||||
* |
||||
* @See GenericApplicationContext.registerBean |
||||
*/ |
||||
inline fun <reified T : Any> bean(name: String? = null, |
||||
scope: Scope? = null, |
||||
isLazyInit: Boolean? = null, |
||||
isPrimary: Boolean? = null, |
||||
isAutowireCandidate: Boolean? = null) { |
||||
|
||||
registrations.add { |
||||
val customizer = BeanDefinitionCustomizer { bd -> |
||||
scope?.let { bd.scope = scope.name.toLowerCase() } |
||||
isLazyInit?.let { bd.isLazyInit = isLazyInit } |
||||
isPrimary?.let { bd.isPrimary = isPrimary } |
||||
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate } |
||||
} |
||||
|
||||
when (name) { |
||||
null -> it.registerBean(T::class.java, customizer) |
||||
else -> it.registerBean(name, T::class.java, customizer) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Declare a bean definition using the given supplier for obtaining a new instance. |
||||
* |
||||
* @See GenericApplicationContext.registerBean |
||||
*/ |
||||
inline fun <reified T : Any> bean(name: String? = null, |
||||
scope: Scope? = null, |
||||
isLazyInit: Boolean? = null, |
||||
isPrimary: Boolean? = null, |
||||
isAutowireCandidate: Boolean? = null, |
||||
crossinline function: (BeanDefinitionContext) -> T) { |
||||
|
||||
val customizer = BeanDefinitionCustomizer { bd -> |
||||
scope?.let { bd.scope = scope.name.toLowerCase() } |
||||
isLazyInit?.let { bd.isLazyInit = isLazyInit } |
||||
isPrimary?.let { bd.isPrimary = isPrimary } |
||||
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate } |
||||
} |
||||
|
||||
registrations.add { |
||||
val beanContext = BeanDefinitionContext(it) |
||||
when (name) { |
||||
null -> it.registerBean(T::class.java, Supplier { function.invoke(beanContext) }, customizer) |
||||
else -> it.registerBean(name, T::class.java, Supplier { function.invoke(beanContext) }, customizer) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Take in account bean definitions enclosed in the provided lambda only when the |
||||
* specified profile is active. |
||||
*/ |
||||
fun profile(profile: String, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { |
||||
val beans = BeanDefinitionDsl({ it.activeProfiles.contains(profile) }) |
||||
beans.init() |
||||
children.add(beans) |
||||
return beans |
||||
} |
||||
|
||||
/** |
||||
* Take in account bean definitions enclosed in the provided lambda only when the |
||||
* specified environment-based predicate is true. |
||||
*/ |
||||
fun environment(condition: (ConfigurableEnvironment) -> Boolean, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { |
||||
val beans = BeanDefinitionDsl(condition::invoke) |
||||
beans.init() |
||||
children.add(beans) |
||||
return beans |
||||
} |
||||
|
||||
override fun invoke(context: GenericApplicationContext) { |
||||
for (registration in registrations) { |
||||
if (condition.invoke(context.environment)) { |
||||
registration.invoke(context) |
||||
} |
||||
} |
||||
for (child in children) { |
||||
child.invoke(context) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Functional bean definition Kotlin DSL. |
||||
* |
||||
* @author Sebastien Deleuze |
||||
* @since 5.0 |
||||
*/ |
||||
fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl { |
||||
val beans = BeanDefinitionDsl() |
||||
beans.init() |
||||
return beans |
||||
} |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright 2002-2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.context.support |
||||
|
||||
import org.junit.Assert.* |
||||
import org.junit.Test |
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException |
||||
import org.springframework.beans.factory.getBean |
||||
import org.springframework.context.support.BeanDefinitionDsl.* |
||||
|
||||
class BeanDefinitionDslTests { |
||||
|
||||
@Test |
||||
fun `Declare beans with the functional Kotlin DSL`() { |
||||
val beans = beans { |
||||
bean<Foo>() |
||||
bean<Bar>("bar", scope = Scope.PROTOTYPE) |
||||
bean { Baz(it.ref<Bar>()) } |
||||
bean { Baz(it.ref("bar")) } |
||||
} |
||||
|
||||
val context = GenericApplicationContext() |
||||
beans.invoke(context) |
||||
context.refresh() |
||||
|
||||
assertNotNull(context.getBean<Foo>()) |
||||
assertNotNull(context.getBean<Bar>("bar")) |
||||
assertTrue(context.isPrototype("bar")) |
||||
assertNotNull(context.getBean<Baz>()) |
||||
} |
||||
|
||||
@Test |
||||
fun `Declare beans using profile condition with the functional Kotlin DSL`() { |
||||
val beans = beans { |
||||
bean<Foo>() |
||||
bean<Bar>("bar") |
||||
profile("baz") { |
||||
profile("pp") { |
||||
bean<Foo>() |
||||
} |
||||
bean { Baz(it.ref<Bar>()) } |
||||
bean { Baz(it.ref("bar")) } |
||||
} |
||||
} |
||||
|
||||
val context = GenericApplicationContext() |
||||
beans.invoke(context) |
||||
context.refresh() |
||||
|
||||
assertNotNull(context.getBean<Foo>()) |
||||
assertNotNull(context.getBean<Bar>("bar")) |
||||
try { |
||||
context.getBean<Baz>() |
||||
fail("Expect NoSuchBeanDefinitionException to be thrown") |
||||
} |
||||
catch(ex: NoSuchBeanDefinitionException) { null } |
||||
} |
||||
|
||||
@Test |
||||
fun `Declare beans using environment condition with the functional Kotlin DSL`() { |
||||
val beans = beans { |
||||
bean<Foo>() |
||||
bean<Bar>("bar") |
||||
environment({it.activeProfiles.contains("baz")}) { |
||||
bean { Baz(it.ref()) } |
||||
bean { Baz(it.ref("bar")) } |
||||
} |
||||
} |
||||
|
||||
val context = GenericApplicationContext() |
||||
beans.invoke(context) |
||||
context.refresh() |
||||
|
||||
assertNotNull(context.getBean<Foo>()) |
||||
assertNotNull(context.getBean<Bar>("bar")) |
||||
try { |
||||
context.getBean<Baz>() |
||||
fail("Expect NoSuchBeanDefinitionException to be thrown") |
||||
} |
||||
catch(ex: NoSuchBeanDefinitionException) { null } |
||||
} |
||||
|
||||
} |
||||
|
||||
class Foo |
||||
class Bar |
||||
class Baz(val bar: Bar) |
||||
Loading…
Reference in new issue