diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index cd7633bb0e9..a187161806f 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -836,6 +836,16 @@ true + + org.jetbrains.kotlin + kotlin-reflect + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + test + org.springframework.boot spring-boot-test-support @@ -993,6 +1003,29 @@ test + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + test-compile + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/java + + + + + + + java9+ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java index 2532aedcc7a..6d2fae82f7c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/diagnostics/analyzer/NoSuchBeanDefinitionFailureAnalyzer.java @@ -46,6 +46,7 @@ import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAnalyzer; import org.springframework.context.annotation.Bean; +import org.springframework.core.KotlinDetector; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -120,10 +121,19 @@ class NoSuchBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnalyz MergedAnnotation configurationProperties = MergedAnnotations.from(declaringClass) .get(ConfigurationProperties.class); if (configurationProperties.isPresent()) { - action = String.format( - "%s%nConsider adding @%s to %s if you intended to use constructor-based " - + "configuration property binding.", - action, ConstructorBinding.class.getSimpleName(), constructor.getName()); + if (KotlinDetector.isKotlinType(declaringClass) && !KotlinDetector.isKotlinReflectPresent()) { + action = String.format( + "%s%nConsider adding a dependency on kotlin-reflect so that the contructor used for @%s can be located. Also, ensure that @%s is present on '%s' if you intended to use constructor-based " + + "configuration property binding.", + action, ConstructorBinding.class.getSimpleName(), ConstructorBinding.class.getSimpleName(), + constructor.getName()); + } + else { + action = String.format( + "%s%nConsider adding @%s to %s if you intended to use constructor-based " + + "configuration property binding.", + action, ConstructorBinding.class.getSimpleName(), constructor.getName()); + } } } return new FailureAnalysis(message.toString(), action, cause); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/kotlin/org/springframework/boot/autoconfigure/diagnostics/analyzer/KotlinNoSuchBeanFailureAnalyzerNoKotlinReflectTests.kt b/spring-boot-project/spring-boot-autoconfigure/src/test/kotlin/org/springframework/boot/autoconfigure/diagnostics/analyzer/KotlinNoSuchBeanFailureAnalyzerNoKotlinReflectTests.kt new file mode 100644 index 00000000000..e112ffa07bd --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/kotlin/org/springframework/boot/autoconfigure/diagnostics/analyzer/KotlinNoSuchBeanFailureAnalyzerNoKotlinReflectTests.kt @@ -0,0 +1,67 @@ +package org.springframework.boot.autoconfigure.diagnostics.analyzer + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.beans.FatalBeanException +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConstructorBinding +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.diagnostics.FailureAnalysis +import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter +import org.springframework.boot.test.util.TestPropertyValues +import org.springframework.boot.testsupport.classpath.ClassPathExclusions +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.context.annotation.Configuration + +/** + * Tests for {@link ConfigurationProperties @ConfigurationProperties}-annotated beans when kotlin-reflect is not present + * on the classpath. + * + * @author Madhura Bhave + */ +@ClassPathExclusions("kotlin-reflect*.jar") +class KotlinNoSuchBeanFailureAnalyzerNoKotlinReflectTests { + + private val analyzer = NoSuchBeanDefinitionFailureAnalyzer() + + @Test + fun failureAnalysisForConfigurationPropertiesThatMaybeShouldHaveBeenConstructorBound() { + val analysis = analyzeFailure( + createFailure(ConstructorBoundConfigurationPropertiesConfiguration::class.java)) + assertThat(analysis!!.getAction()).startsWith( + java.lang.String.format("Consider defining a bean of type '%s' in your configuration.", String::class.java!!.getName())) + assertThat(analysis!!.getAction()).contains(java.lang.String.format( + "Consider adding a dependency on kotlin-reflect so that the contructor used for @ConstructorBinding can be located. Also, ensure that @ConstructorBinding is present on '%s' ", ConstructorBoundProperties::class.java!!.getName())) + } + + private fun createFailure(config: Class<*>, vararg environment: String): FatalBeanException? { + try { + AnnotationConfigApplicationContext().use { context -> + this.analyzer.setBeanFactory(context.beanFactory) + TestPropertyValues.of(*environment).applyTo(context) + context.register(config) + context.refresh() + return null + } + } catch (ex: FatalBeanException) { + return ex + } + + } + + private fun analyzeFailure(failure: Exception?): FailureAnalysis? { + val analysis = this.analyzer.analyze(failure) + if (analysis != null) { + LoggingFailureAnalysisReporter().report(analysis) + } + return analysis + } + + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(ConstructorBoundProperties::class) + internal class ConstructorBoundConfigurationPropertiesConfiguration + + @ConfigurationProperties("test") + @ConstructorBinding + internal class ConstructorBoundProperties(val name: String) +} \ No newline at end of file