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,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
}