Improve diagnostics when using @ConstructorBinding on a Kotlin class

Closes gh-19312
This commit is contained in:
Madhura Bhave 2020-01-08 13:51:21 -08:00
parent 02965e9744
commit 437941cc51
3 changed files with 114 additions and 4 deletions

View File

@ -836,6 +836,16 @@
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-support</artifactId>
@ -993,6 +1003,29 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>java9+</id>

View File

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

View File

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