From cd365bcae1ed96d07e6bacc0fff8e3f1fde8a068 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 13 May 2016 20:00:48 -0700 Subject: [PATCH] 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 --- .../PropertyMappingContextCustomizer.java | 52 ++++++++++++++++--- ...yMappingContextCustomizerFactoryTests.java | 5 +- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizer.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizer.java index f57a1478e14..f6dcc3f594b 100644 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizer.java +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizer.java @@ -16,6 +16,10 @@ package org.springframework.boot.test.autoconfigure.properties; +import java.lang.annotation.Annotation; +import java.util.LinkedHashSet; +import java.util.Set; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ConfigurableApplicationContext; @@ -24,7 +28,7 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.MergedContextConfiguration; -import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * {@link ContextCustomizer} to map annotation attributes to {@link Environment} @@ -72,17 +76,49 @@ class PropertyMappingContextCustomizer implements ContextCustomizer { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Class beanClass = bean.getClass(); - boolean hasComponent = AnnotationUtils.findAnnotation(beanClass, - Component.class) != null; - boolean hasPropertyMapping = AnnotationUtils.findAnnotation(beanClass, - PropertyMapping.class) != null; - if (hasComponent) { - Assert.state(!hasPropertyMapping, - "@PropertyMapping annotations can only be used on test classes"); + Set> components = new LinkedHashSet>(); + Set> propertyMappings = new LinkedHashSet>(); + while (beanClass != null) { + for (Annotation annotation : AnnotationUtils.getAnnotations(beanClass)) { + if (isAnnotated(annotation, Component.class)) { + components.add(annotation.annotationType()); + } + if (isAnnotated(annotation, PropertyMapping.class)) { + propertyMappings.add(annotation.annotationType()); + } + } + beanClass = beanClass.getSuperclass(); + } + if (!components.isEmpty() && !propertyMappings.isEmpty()) { + throw new IllegalStateException("The @PropertyMapping " + + getAnnotationsDescription(propertyMappings) + + " cannot be used in combination with the @Component " + + getAnnotationsDescription(components)); } return bean; } + private boolean isAnnotated(Annotation element, + Class annotationType) { + try { + return element.annotationType().equals(annotationType) || AnnotationUtils + .findAnnotation(element.annotationType(), annotationType) != null; + } + catch (Throwable ex) { + return false; + } + } + + private String getAnnotationsDescription(Set> annotations) { + StringBuilder result = new StringBuilder(); + for (Class annotation : annotations) { + result.append(result.length() == 0 ? "" : ", "); + result.append("@" + ClassUtils.getShortName(annotation)); + } + result.insert(0, annotations.size() == 1 ? "annotation " : "annotations "); + return result.toString(); + } + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizerFactoryTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizerFactoryTests.java index 70f95c286c2..c99cae6d181 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizerFactoryTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/PropertyMappingContextCustomizerFactoryTests.java @@ -107,8 +107,9 @@ public class PropertyMappingContextCustomizerFactoryTests { context.register(ConfigMapping.class); customizer.customizeContext(context, null); this.thrown.expect(BeanCreationException.class); - this.thrown.expectMessage( - "@PropertyMapping annotations can only be used on test classes"); + this.thrown.expectMessage("The @PropertyMapping annotation " + + "@PropertyMappingContextCustomizerFactoryTests.TypeMappingAnnotation " + + "cannot be used in combination with the @Component annotation @Configuration"); context.refresh(); }