Skip to content

Commit 1f01146

Browse files
committed
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
1 parent f4180eb commit 1f01146

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.support
18+
19+
import org.springframework.beans.factory.config.BeanDefinitionCustomizer
20+
import org.springframework.context.ApplicationContext
21+
import org.springframework.core.env.ConfigurableEnvironment
22+
import java.util.function.Supplier
23+
24+
/**
25+
* Class implementing functional bean definition Kotlin DSL.
26+
*
27+
* @author Sebastien Deleuze
28+
* @since 5.0
29+
*/
30+
open class BeanDefinitionDsl(val condition: (ConfigurableEnvironment) -> Boolean = { true }) : (GenericApplicationContext) -> Unit {
31+
32+
protected val registrations = arrayListOf<(GenericApplicationContext) -> Unit>()
33+
34+
protected val children = arrayListOf<BeanDefinitionDsl>()
35+
36+
enum class Scope {
37+
SINGLETON,
38+
PROTOTYPE
39+
}
40+
41+
class BeanDefinitionContext(val context: ApplicationContext) {
42+
43+
inline fun <reified T : Any> ref(name: String? = null) : T = when (name) {
44+
null -> context.getBean(T::class.java)
45+
else -> context.getBean(name, T::class.java)
46+
}
47+
}
48+
49+
/**
50+
* Declare a bean definition from the given bean class which can be inferred when possible.
51+
*
52+
* @See GenericApplicationContext.registerBean
53+
*/
54+
inline fun <reified T : Any> bean(name: String? = null,
55+
scope: Scope? = null,
56+
isLazyInit: Boolean? = null,
57+
isPrimary: Boolean? = null,
58+
isAutowireCandidate: Boolean? = null) {
59+
60+
registrations.add {
61+
val customizer = BeanDefinitionCustomizer { bd ->
62+
scope?.let { bd.scope = scope.name.toLowerCase() }
63+
isLazyInit?.let { bd.isLazyInit = isLazyInit }
64+
isPrimary?.let { bd.isPrimary = isPrimary }
65+
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
66+
}
67+
68+
when (name) {
69+
null -> it.registerBean(T::class.java, customizer)
70+
else -> it.registerBean(name, T::class.java, customizer)
71+
}
72+
}
73+
}
74+
75+
/**
76+
* Declare a bean definition using the given supplier for obtaining a new instance.
77+
*
78+
* @See GenericApplicationContext.registerBean
79+
*/
80+
inline fun <reified T : Any> bean(name: String? = null,
81+
scope: Scope? = null,
82+
isLazyInit: Boolean? = null,
83+
isPrimary: Boolean? = null,
84+
isAutowireCandidate: Boolean? = null,
85+
crossinline function: (BeanDefinitionContext) -> T) {
86+
87+
val customizer = BeanDefinitionCustomizer { bd ->
88+
scope?.let { bd.scope = scope.name.toLowerCase() }
89+
isLazyInit?.let { bd.isLazyInit = isLazyInit }
90+
isPrimary?.let { bd.isPrimary = isPrimary }
91+
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
92+
}
93+
94+
registrations.add {
95+
val beanContext = BeanDefinitionContext(it)
96+
when (name) {
97+
null -> it.registerBean(T::class.java, Supplier { function.invoke(beanContext) }, customizer)
98+
else -> it.registerBean(name, T::class.java, Supplier { function.invoke(beanContext) }, customizer)
99+
}
100+
}
101+
}
102+
103+
/**
104+
* Take in account bean definitions enclosed in the provided lambda only when the
105+
* specified profile is active.
106+
*/
107+
fun profile(profile: String, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
108+
val beans = BeanDefinitionDsl({ it.activeProfiles.contains(profile) })
109+
beans.init()
110+
children.add(beans)
111+
return beans
112+
}
113+
114+
/**
115+
* Take in account bean definitions enclosed in the provided lambda only when the
116+
* specified environment-based predicate is true.
117+
*/
118+
fun environment(condition: (ConfigurableEnvironment) -> Boolean, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
119+
val beans = BeanDefinitionDsl(condition::invoke)
120+
beans.init()
121+
children.add(beans)
122+
return beans
123+
}
124+
125+
override fun invoke(context: GenericApplicationContext) {
126+
for (registration in registrations) {
127+
if (condition.invoke(context.environment)) {
128+
registration.invoke(context)
129+
}
130+
}
131+
for (child in children) {
132+
child.invoke(context)
133+
}
134+
}
135+
}
136+
137+
/**
138+
* Functional bean definition Kotlin DSL.
139+
*
140+
* @author Sebastien Deleuze
141+
* @since 5.0
142+
*/
143+
fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
144+
val beans = BeanDefinitionDsl()
145+
beans.init()
146+
return beans
147+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.support
18+
19+
import org.junit.Assert.*
20+
import org.junit.Test
21+
import org.springframework.beans.factory.NoSuchBeanDefinitionException
22+
import org.springframework.beans.factory.getBean
23+
import org.springframework.context.support.BeanDefinitionDsl.*
24+
25+
class BeanDefinitionDslTests {
26+
27+
@Test
28+
fun `Declare beans with the functional Kotlin DSL`() {
29+
val beans = beans {
30+
bean<Foo>()
31+
bean<Bar>("bar", scope = Scope.PROTOTYPE)
32+
bean { Baz(it.ref<Bar>()) }
33+
bean { Baz(it.ref("bar")) }
34+
}
35+
36+
val context = GenericApplicationContext()
37+
beans.invoke(context)
38+
context.refresh()
39+
40+
assertNotNull(context.getBean<Foo>())
41+
assertNotNull(context.getBean<Bar>("bar"))
42+
assertTrue(context.isPrototype("bar"))
43+
assertNotNull(context.getBean<Baz>())
44+
}
45+
46+
@Test
47+
fun `Declare beans using profile condition with the functional Kotlin DSL`() {
48+
val beans = beans {
49+
bean<Foo>()
50+
bean<Bar>("bar")
51+
profile("baz") {
52+
profile("pp") {
53+
bean<Foo>()
54+
}
55+
bean { Baz(it.ref<Bar>()) }
56+
bean { Baz(it.ref("bar")) }
57+
}
58+
}
59+
60+
val context = GenericApplicationContext()
61+
beans.invoke(context)
62+
context.refresh()
63+
64+
assertNotNull(context.getBean<Foo>())
65+
assertNotNull(context.getBean<Bar>("bar"))
66+
try {
67+
context.getBean<Baz>()
68+
fail("Expect NoSuchBeanDefinitionException to be thrown")
69+
}
70+
catch(ex: NoSuchBeanDefinitionException) { null }
71+
}
72+
73+
@Test
74+
fun `Declare beans using environment condition with the functional Kotlin DSL`() {
75+
val beans = beans {
76+
bean<Foo>()
77+
bean<Bar>("bar")
78+
environment({it.activeProfiles.contains("baz")}) {
79+
bean { Baz(it.ref()) }
80+
bean { Baz(it.ref("bar")) }
81+
}
82+
}
83+
84+
val context = GenericApplicationContext()
85+
beans.invoke(context)
86+
context.refresh()
87+
88+
assertNotNull(context.getBean<Foo>())
89+
assertNotNull(context.getBean<Bar>("bar"))
90+
try {
91+
context.getBean<Baz>()
92+
fail("Expect NoSuchBeanDefinitionException to be thrown")
93+
}
94+
catch(ex: NoSuchBeanDefinitionException) { null }
95+
}
96+
97+
}
98+
99+
class Foo
100+
class Bar
101+
class Baz(val bar: Bar)

0 commit comments

Comments
 (0)