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:
Sebastien Deleuze
2017-07-11 12:05:24 +02:00
parent f4180eb359
commit 1f011467b8
2 changed files with 248 additions and 0 deletions

View File

@@ -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)