From 1669b81af76ca4ad4287d9b3affed3b79ee0048d Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 5 Jun 2023 21:18:58 -0700 Subject: [PATCH] Add 'fromApplication' and 'with' Kotlin extension functions Update `SpringApplicationExtensions.kt` with `fromApplication` and `with` functions that make `SpringApplication.from(...)` easier to use with Kotlin. Fixes gh-35756 --- .../launch/TestMyApplication.kt | 10 ++---- .../test/TestMyApplication.kt | 13 +++---- .../boot/SpringApplicationExtensions.kt | 36 +++++++++++++++++++ .../boot/SpringApplicationExtensionsTests.kt | 14 ++++++++ .../kotlinsample/TestKotlinApplication.kt | 11 ++++++ 5 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/kotlinsample/TestKotlinApplication.kt diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/launch/TestMyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/launch/TestMyApplication.kt index d4f2ea76cc7..3aac403b306 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/launch/TestMyApplication.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/launch/TestMyApplication.kt @@ -16,12 +16,8 @@ package org.springframework.boot.docs.features.testing.testcontainers.atdevelopmenttime.launch -import org.springframework.boot.SpringApplication -import org.springframework.boot.docs.features.testing.testcontainers.atdevelopmenttime.launch.main as myApplicationMain +import org.springframework.boot.fromApplication -object Main { - @JvmStatic - fun main(args: Array) { - SpringApplication.from(::myApplicationMain).run(*args) - } +fun main(args: Array) { + fromApplication().run(*args) } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/test/TestMyApplication.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/test/TestMyApplication.kt index 0b7e4a17f18..8918374d612 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/test/TestMyApplication.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/testing/testcontainers/atdevelopmenttime/test/TestMyApplication.kt @@ -16,12 +16,9 @@ package org.springframework.boot.docs.features.testing.testcontainers.atdevelopmenttime.test -import org.springframework.boot.SpringApplication -import org.springframework.boot.docs.features.testing.testcontainers.atdevelopmenttime.test.main as myApplicationMain +import org.springframework.boot.fromApplication +import org.springframework.boot.with -object Main { - @JvmStatic - fun main(args: Array) { - SpringApplication.from(::myApplicationMain).with(MyContainersConfiguration::class.java).run(*args) - } -} \ No newline at end of file +fun main(args: Array) { + fromApplication().with(MyContainersConfiguration::class).run(*args) +} diff --git a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt index bbfdbdb5cd7..5b911a8ee7e 100644 --- a/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt +++ b/spring-boot-project/spring-boot/src/main/kotlin/org/springframework/boot/SpringApplicationExtensions.kt @@ -17,6 +17,11 @@ package org.springframework.boot import org.springframework.context.ConfigurableApplicationContext +import org.springframework.util.Assert +import org.springframework.util.ClassUtils +import org.springframework.util.ReflectionUtils +import kotlin.reflect.KClass +import kotlin.reflect.KType /** * Top-level function acting as a Kotlin shortcut allowing to write @@ -40,3 +45,34 @@ inline fun runApplication(vararg args: String): ConfigurableAp */ inline fun runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext = SpringApplication(T::class.java).apply(init).run(*args) + +/** + * Top-level function acting as a Kotlin shortcut allowing to write + * `fromApplication().with(...)`. This method assumes that + * the `main` function is declared in the same file as `T`. + * + * @author Phillip Webb + * @since 3.1.1 + */ +inline fun fromApplication(): SpringApplication.Augmented { + val type = T::class + val ktClassName = type.qualifiedName + "Kt" + try { + val ktClass = ClassUtils.resolveClassName(ktClassName, type.java.classLoader) + val mainMethod = ReflectionUtils.findMethod(ktClass, "main", Array::class.java) + Assert.notNull(mainMethod, "Unable to find main method") + return SpringApplication.from { ReflectionUtils.invokeMethod(mainMethod!!, null, it) } + } catch (ex: Exception) { + throw IllegalStateException("Unable to use 'fromApplication' with " + type.qualifiedName) + } +} + +/** + * Extension function that allows `SpringApplication.Augmented.with` to work with Kotlin classes. + * + * @author Phillip Webb + * @since 3.1.1 + */ +fun SpringApplication.Augmented.with(type: KClass<*>): SpringApplication.Augmented { + return this.with(type.java)!! +} diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/SpringApplicationExtensionsTests.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/SpringApplicationExtensionsTests.kt index d1da352d9e7..7810b2eb1a3 100644 --- a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/SpringApplicationExtensionsTests.kt +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/SpringApplicationExtensionsTests.kt @@ -16,9 +16,11 @@ package org.springframework.boot import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalStateException import org.junit.jupiter.api.Test import org.springframework.beans.factory.getBean +import org.springframework.boot.kotlinsample.TestKotlinApplication import org.springframework.boot.web.servlet.server.MockServletWebServerFactory import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -67,6 +69,18 @@ class SpringApplicationExtensionsTests { assertThat(environment).isEqualTo(context.environment) } + @Test + fun `Kotlin fromApplication() top level function`() { + val context = fromApplication().with(ExampleWebConfig::class).run().applicationContext + assertThat(context.getBean()).isNotNull + } + + @Test + fun `Kotlin fromApplication() top level function when no main`() { + assertThatIllegalStateException().isThrownBy { fromApplication().run() } + .withMessage("Unable to use 'fromApplication' with org.springframework.boot.SpringApplicationExtensionsTests.ExampleWebConfig") + } + @Configuration(proxyBeanMethods = false) internal open class ExampleWebConfig { diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/kotlinsample/TestKotlinApplication.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/kotlinsample/TestKotlinApplication.kt new file mode 100644 index 00000000000..017a7d52a3c --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/kotlinsample/TestKotlinApplication.kt @@ -0,0 +1,11 @@ +package org.springframework.boot.kotlinsample + +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Configuration + +@Configuration(proxyBeanMethods = false) +open class TestKotlinApplication + +fun main(args: Array) { + runApplication(*args) +}