Skip to content

Commit 928511b

Browse files
committed
Improve @PropertyMapping error message
Improve the message thrown when a @PropertyMapping is used in combination with a @component to include the actual annotations that are causing the problem. Fixes gh-5897
1 parent 434ab5d commit 928511b

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizer.java

+44-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot.test.autoconfigure.properties;
1818

19+
import java.lang.annotation.Annotation;
20+
import java.util.LinkedHashSet;
21+
import java.util.Set;
22+
1923
import org.springframework.beans.BeansException;
2024
import org.springframework.beans.factory.config.BeanPostProcessor;
2125
import org.springframework.context.ConfigurableApplicationContext;
@@ -24,7 +28,7 @@
2428
import org.springframework.stereotype.Component;
2529
import org.springframework.test.context.ContextCustomizer;
2630
import org.springframework.test.context.MergedContextConfiguration;
27-
import org.springframework.util.Assert;
31+
import org.springframework.util.ClassUtils;
2832

2933
/**
3034
* {@link ContextCustomizer} to map annotation attributes to {@link Environment}
@@ -72,17 +76,49 @@ static class PropertyMappingCheckBeanPostProcessor implements BeanPostProcessor
7276
public Object postProcessBeforeInitialization(Object bean, String beanName)
7377
throws BeansException {
7478
Class<?> beanClass = bean.getClass();
75-
boolean hasComponent = AnnotationUtils.findAnnotation(beanClass,
76-
Component.class) != null;
77-
boolean hasPropertyMapping = AnnotationUtils.findAnnotation(beanClass,
78-
PropertyMapping.class) != null;
79-
if (hasComponent) {
80-
Assert.state(!hasPropertyMapping,
81-
"@PropertyMapping annotations can only be used on test classes");
79+
Set<Class<?>> components = new LinkedHashSet<Class<?>>();
80+
Set<Class<?>> propertyMappings = new LinkedHashSet<Class<?>>();
81+
while (beanClass != null) {
82+
for (Annotation annotation : AnnotationUtils.getAnnotations(beanClass)) {
83+
if (isAnnotated(annotation, Component.class)) {
84+
components.add(annotation.annotationType());
85+
}
86+
if (isAnnotated(annotation, PropertyMapping.class)) {
87+
propertyMappings.add(annotation.annotationType());
88+
}
89+
}
90+
beanClass = beanClass.getSuperclass();
91+
}
92+
if (!components.isEmpty() && !propertyMappings.isEmpty()) {
93+
throw new IllegalStateException("The @PropertyMapping "
94+
+ getAnnotationsDescription(propertyMappings)
95+
+ " cannot be used in combination with the @Component "
96+
+ getAnnotationsDescription(components));
8297
}
8398
return bean;
8499
}
85100

101+
private boolean isAnnotated(Annotation element,
102+
Class<? extends Annotation> annotationType) {
103+
try {
104+
return element.annotationType().equals(annotationType) || AnnotationUtils
105+
.findAnnotation(element.annotationType(), annotationType) != null;
106+
}
107+
catch (Throwable ex) {
108+
return false;
109+
}
110+
}
111+
112+
private String getAnnotationsDescription(Set<Class<?>> annotations) {
113+
StringBuilder result = new StringBuilder();
114+
for (Class<?> annotation : annotations) {
115+
result.append(result.length() == 0 ? "" : ", ");
116+
result.append("@" + ClassUtils.getShortName(annotation));
117+
}
118+
result.insert(0, annotations.size() == 1 ? "annotation " : "annotations ");
119+
return result.toString();
120+
}
121+
86122
@Override
87123
public Object postProcessAfterInitialization(Object bean, String beanName)
88124
throws BeansException {

spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizerFactoryTests.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ public void propertyMappingShouldNotBeUsedWithComponent() throws Exception {
107107
context.register(ConfigMapping.class);
108108
customizer.customizeContext(context, null);
109109
this.thrown.expect(BeanCreationException.class);
110-
this.thrown.expectMessage(
111-
"@PropertyMapping annotations can only be used on test classes");
110+
this.thrown.expectMessage("The @PropertyMapping annotation "
111+
+ "@PropertyMappingContextCustomizerFactoryTests.TypeMappingAnnotation "
112+
+ "cannot be used in combination with the @Component annotation @Configuration");
112113
context.refresh();
113114
}
114115

0 commit comments

Comments
 (0)