Skip to content

Commit 97ee94f

Browse files
committed
Expose env and context in Kotlin beans DSL
This commit introduces a deferred initialization of the declared beans in order to make it possible to access to the environment (and even to the context for advanced use-cases) in the beans { } Kotlin DSL. Issues: SPR-16269, SPR-16412
1 parent 8317f12 commit 97ee94f

File tree

2 files changed

+100
-71
lines changed

2 files changed

+100
-71
lines changed

spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt

Lines changed: 57 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ import java.util.function.Supplier
6363
*/
6464
fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
6565
val beans = BeanDefinitionDsl()
66-
beans.init()
66+
beans.init = init
6767
return beans
6868
}
6969

@@ -79,12 +79,24 @@ fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
7979
open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) -> Boolean = { true })
8080
: ApplicationContextInitializer<GenericApplicationContext> {
8181

82-
@PublishedApi
83-
internal val registrations = arrayListOf<(GenericApplicationContext) -> Unit>()
84-
8582
@PublishedApi
8683
internal val children = arrayListOf<BeanDefinitionDsl>()
8784

85+
internal lateinit var init: BeanDefinitionDsl.() -> Unit
86+
87+
/**
88+
* Access to the context for advanced use-cases.
89+
* @since 5.1
90+
*/
91+
lateinit var context: GenericApplicationContext
92+
93+
/**
94+
* Shortcut for `context.environment`
95+
* @since 5.1
96+
*/
97+
val env : ConfigurableEnvironment
98+
get() = context.environment
99+
88100
/**
89101
* Scope enum constants.
90102
*/
@@ -130,34 +142,6 @@ open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) ->
130142

131143
}
132144

133-
/**
134-
* Provide read access to some application context facilities.
135-
* @constructor Create a new bean definition context.
136-
* @param context the `ApplicationContext` instance to use for retrieving bean
137-
* references, `Environment`, etc.
138-
*/
139-
inner class BeanDefinitionContext(@PublishedApi internal val context: GenericApplicationContext) {
140-
141-
/**
142-
* Get a reference to the bean by type or type + name with the syntax
143-
* `ref<Foo>()` or `ref<Foo>("foo")`. When leveraging Kotlin type inference
144-
* it could be as short as `ref()` or `ref("foo")`.
145-
* @param name the name of the bean to retrieve
146-
* @param T type the bean must match, can be an interface or superclass
147-
*/
148-
inline fun <reified T : Any> ref(name: String? = null) : T = when (name) {
149-
null -> context.getBean(T::class.java)
150-
else -> context.getBean(name, T::class.java)
151-
}
152-
153-
/**
154-
* Get the [ConfigurableEnvironment] associated to the underlying [GenericApplicationContext].
155-
*/
156-
val env : ConfigurableEnvironment
157-
get() = context.environment
158-
159-
}
160-
161145
/**
162146
* Declare a bean definition from the given bean class which can be inferred when possible.
163147
*
@@ -177,23 +161,22 @@ open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) ->
177161
isPrimary: Boolean? = null,
178162
autowireMode: Autowire = Autowire.CONSTRUCTOR,
179163
isAutowireCandidate: Boolean? = null) {
180-
181-
registrations.add {
182-
val customizer = BeanDefinitionCustomizer { bd ->
183-
scope?.let { bd.scope = scope.name.toLowerCase() }
184-
isLazyInit?.let { bd.isLazyInit = isLazyInit }
185-
isPrimary?.let { bd.isPrimary = isPrimary }
186-
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
187-
if (bd is AbstractBeanDefinition) {
188-
bd.autowireMode = autowireMode.ordinal
189-
}
190-
}
191-
192-
when (name) {
193-
null -> it.registerBean(T::class.java, customizer)
194-
else -> it.registerBean(name, T::class.java, customizer)
164+
165+
val customizer = BeanDefinitionCustomizer { bd ->
166+
scope?.let { bd.scope = scope.name.toLowerCase() }
167+
isLazyInit?.let { bd.isLazyInit = isLazyInit }
168+
isPrimary?.let { bd.isPrimary = isPrimary }
169+
isAutowireCandidate?.let { bd.isAutowireCandidate = isAutowireCandidate }
170+
if (bd is AbstractBeanDefinition) {
171+
bd.autowireMode = autowireMode.ordinal
195172
}
196173
}
174+
175+
when (name) {
176+
null -> context.registerBean(T::class.java, customizer)
177+
else -> context.registerBean(name, T::class.java, customizer)
178+
}
179+
197180
}
198181

199182
/**
@@ -216,7 +199,7 @@ open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) ->
216199
isPrimary: Boolean? = null,
217200
autowireMode: Autowire = Autowire.NO,
218201
isAutowireCandidate: Boolean? = null,
219-
crossinline function: BeanDefinitionContext.() -> T) {
202+
crossinline function: () -> T) {
220203

221204
val customizer = BeanDefinitionCustomizer { bd ->
222205
scope?.let { bd.scope = scope.name.toLowerCase() }
@@ -228,26 +211,36 @@ open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) ->
228211
}
229212
}
230213

231-
registrations.add {
232-
val beanContext = BeanDefinitionContext(it)
233-
when (name) {
234-
null -> it.registerBean(T::class.java,
235-
Supplier { function.invoke(beanContext) }, customizer)
236-
else -> it.registerBean(name, T::class.java,
237-
Supplier { function.invoke(beanContext) }, customizer)
238-
}
214+
215+
when (name) {
216+
null -> context.registerBean(T::class.java,
217+
Supplier { function.invoke() }, customizer)
218+
else -> context.registerBean(name, T::class.java,
219+
Supplier { function.invoke() }, customizer)
239220
}
221+
222+
}
223+
224+
/**
225+
* Get a reference to the bean by type or type + name with the syntax
226+
* `ref<Foo>()` or `ref<Foo>("foo")`. When leveraging Kotlin type inference
227+
* it could be as short as `ref()` or `ref("foo")`.
228+
* @param name the name of the bean to retrieve
229+
* @param T type the bean must match, can be an interface or superclass
230+
*/
231+
inline fun <reified T : Any> ref(name: String? = null) : T = when (name) {
232+
null -> context.getBean(T::class.java)
233+
else -> context.getBean(name, T::class.java)
240234
}
241235

242236
/**
243237
* Take in account bean definitions enclosed in the provided lambda only when the
244238
* specified profile is active.
245239
*/
246-
fun profile(profile: String, init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
240+
fun profile(profile: String, init: BeanDefinitionDsl.() -> Unit) {
247241
val beans = BeanDefinitionDsl({ it.activeProfiles.contains(profile) })
248-
beans.init()
242+
beans.init = init
249243
children.add(beans)
250-
return beans
251244
}
252245

253246
/**
@@ -257,25 +250,21 @@ open class BeanDefinitionDsl(private val condition: (ConfigurableEnvironment) ->
257250
* bean definition block
258251
*/
259252
fun environment(condition: ConfigurableEnvironment.() -> Boolean,
260-
init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl {
253+
init: BeanDefinitionDsl.() -> Unit) {
261254
val beans = BeanDefinitionDsl(condition::invoke)
262-
beans.init()
255+
beans.init = init
263256
children.add(beans)
264-
return beans
265257
}
266258

267259
/**
268260
* Register the bean defined via the DSL on the provided application context.
269261
* @param context The `ApplicationContext` to use for registering the beans
270262
*/
271263
override fun initialize(context: GenericApplicationContext) {
272-
for (registration in registrations) {
273-
if (condition.invoke(context.environment)) {
274-
registration.invoke(context)
275-
}
276-
}
264+
this.context = context
277265
for (child in children) {
278266
child.initialize(context)
279267
}
268+
init()
280269
}
281270
}

spring-context/src/test/kotlin/org/springframework/context/support/BeanDefinitionDslTests.kt

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,16 +20,18 @@ import org.junit.Assert.*
2020
import org.junit.Test
2121
import org.springframework.beans.factory.NoSuchBeanDefinitionException
2222
import org.springframework.beans.factory.getBean
23+
import org.springframework.beans.factory.getBeansOfType
2324
import org.springframework.context.support.BeanDefinitionDsl.*
2425
import org.springframework.core.env.SimpleCommandLinePropertySource
2526
import org.springframework.core.env.get
27+
import org.springframework.mock.env.MockPropertySource
2628

2729
@Suppress("UNUSED_EXPRESSION")
2830
class BeanDefinitionDslTests {
2931

3032
@Test
3133
fun `Declare beans with the functional Kotlin DSL`() {
32-
val beans = beans {
34+
val beans = beans {
3335
bean<Foo>()
3436
bean<Bar>("bar", scope = Scope.PROTOTYPE)
3537
bean { Baz(ref()) }
@@ -87,7 +89,7 @@ class BeanDefinitionDslTests {
8789
}
8890
}
8991

90-
val context = GenericApplicationContext().apply {
92+
val context = GenericApplicationContext().apply {
9193
environment.propertySources.addFirst(SimpleCommandLinePropertySource("--name=foofoo"))
9294
beans.initialize(this)
9395
refresh()
@@ -103,10 +105,48 @@ class BeanDefinitionDslTests {
103105
val foofoo = context.getBean<FooFoo>()
104106
assertEquals("foofoo", foofoo.name)
105107
}
108+
109+
@Test // SPR-16412
110+
fun `Declare beans depending on environment properties`() {
111+
val beans = beans {
112+
val n = env["number-of-beans"].toInt()
113+
for (i in 1..n) {
114+
bean("string$i") { Foo() }
115+
}
116+
}
117+
118+
val context = GenericApplicationContext().apply {
119+
environment.propertySources.addLast(MockPropertySource().withProperty("number-of-beans", 5))
120+
beans.initialize(this)
121+
refresh()
122+
}
123+
124+
for (i in 1..5) {
125+
assertNotNull(context.getBean("string$i"))
126+
}
127+
}
128+
129+
@Test // SPR-16269
130+
fun `Provide access to the context for allowing calling advanced features like getBeansOfType`() {
131+
val beans = beans {
132+
bean<Foo>("foo1")
133+
bean<Foo>("foo2")
134+
bean { BarBar(context.getBeansOfType<Foo>().values) }
135+
}
136+
137+
val context = GenericApplicationContext().apply {
138+
beans.initialize(this)
139+
refresh()
140+
}
141+
142+
val barbar = context.getBean<BarBar>()
143+
assertEquals(2, barbar.foos.size)
144+
}
106145

107146
}
108147

109148
class Foo
110149
class Bar
111150
class Baz(val bar: Bar)
112151
class FooFoo(val name: String)
152+
class BarBar(val foos: Collection<Foo>)

0 commit comments

Comments
 (0)