diff --git a/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt b/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt new file mode 100644 index 00000000000..8bf012e7344 --- /dev/null +++ b/spring-context/src/main/kotlin/org/springframework/context/support/BeanDefinitionDsl.kt @@ -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() + + enum class Scope { + SINGLETON, + PROTOTYPE + } + + class BeanDefinitionContext(val context: ApplicationContext) { + + inline fun 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 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 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 +} diff --git a/spring-context/src/test/kotlin/org/springframework/context/support/BeanDefinitionDslTests.kt b/spring-context/src/test/kotlin/org/springframework/context/support/BeanDefinitionDslTests.kt new file mode 100644 index 00000000000..690e3acc06f --- /dev/null +++ b/spring-context/src/test/kotlin/org/springframework/context/support/BeanDefinitionDslTests.kt @@ -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() + bean("bar", scope = Scope.PROTOTYPE) + bean { Baz(it.ref()) } + bean { Baz(it.ref("bar")) } + } + + val context = GenericApplicationContext() + beans.invoke(context) + context.refresh() + + assertNotNull(context.getBean()) + assertNotNull(context.getBean("bar")) + assertTrue(context.isPrototype("bar")) + assertNotNull(context.getBean()) + } + + @Test + fun `Declare beans using profile condition with the functional Kotlin DSL`() { + val beans = beans { + bean() + bean("bar") + profile("baz") { + profile("pp") { + bean() + } + bean { Baz(it.ref()) } + bean { Baz(it.ref("bar")) } + } + } + + val context = GenericApplicationContext() + beans.invoke(context) + context.refresh() + + assertNotNull(context.getBean()) + assertNotNull(context.getBean("bar")) + try { + context.getBean() + 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() + bean("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()) + assertNotNull(context.getBean("bar")) + try { + context.getBean() + fail("Expect NoSuchBeanDefinitionException to be thrown") + } + catch(ex: NoSuchBeanDefinitionException) { null } + } + +} + +class Foo +class Bar +class Baz(val bar: Bar)