Introduce Kotlin functional bean definition DSL
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
This commit is contained in:
parent
f4180eb359
commit
1f011467b8
|
@ -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 @@
|
|||
/*
|
||||
* 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