Synthesize annotation from map w/ minimal attributes
The initial support for synthesizing an annotation from a Map (or AnnotationAttributes) introduced in SPR-13067 required that the map contain key-value pairs for every attribute defined by the supplied annotationType. However, there are use cases that would benefit from being able to supply a reduced set of attributes and still have the annotation synthesized properly. This commit refines the validation mechanism in MapAnnotationAttributeExtractor so that a reduced set of attributes may be supplied. Specifically, if an attribute is missing in the supplied map the attribute will be set either to value of its alias (if an alias value configured via @AliasFor exists) or to the value of the attribute's default value (if defined), and otherwise an exception will be thrown. Furthermore, TransactionalTestExecutionListener has been refactored to take advantage of this new feature by synthesizing an instance of @TransactionConfiguration solely from the default values of its declared attributes. Issue: SPR-13087
This commit is contained in:
@@ -24,6 +24,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -55,6 +56,8 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
*/
|
||||
public class AnnotationUtilsTests {
|
||||
|
||||
private static final Map<String, Object> EMPTY_ATTRS = Collections.emptyMap();
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@@ -688,25 +691,52 @@ public class AnnotationUtilsTests {
|
||||
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithEmptyAttributesWithDefaultsWithoutAttributeAliases() throws Exception {
|
||||
AnnotationWithDefaults annotationWithDefaults = synthesizeAnnotation(EMPTY_ATTRS, AnnotationWithDefaults.class, null);
|
||||
assertNotNull(annotationWithDefaults);
|
||||
assertEquals("text: ", "enigma", annotationWithDefaults.text());
|
||||
assertTrue("predicate: ", annotationWithDefaults.predicate());
|
||||
assertArrayEquals("characters: ", new char[] { 'a', 'b', 'c' }, annotationWithDefaults.characters());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithEmptyAttributesWithDefaultsWithAttributeAliases() throws Exception {
|
||||
ContextConfig contextConfig = synthesizeAnnotation(EMPTY_ATTRS, ContextConfig.class, null);
|
||||
assertNotNull(contextConfig);
|
||||
assertEquals("value: ", "", contextConfig.value());
|
||||
assertEquals("locations: ", "", contextConfig.locations());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithMinimalAttributesWithAttributeAliases() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("locations", "test.xml");
|
||||
ContextConfig contextConfig = synthesizeAnnotation(map, ContextConfig.class, null);
|
||||
assertNotNull(contextConfig);
|
||||
assertEquals("value: ", "test.xml", contextConfig.value());
|
||||
assertEquals("locations: ", "test.xml", contextConfig.locations());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(startsWith("Attributes map"));
|
||||
exception.expectMessage(containsString("returned null for required attribute [value]"));
|
||||
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
|
||||
synthesizeAnnotation(new HashMap<String, Object>(), Component.class, null);
|
||||
assertMissingTextAttribute(EMPTY_ATTRS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put(VALUE, null);
|
||||
map.put("text", null);
|
||||
assertTrue(map.containsKey("text"));
|
||||
assertMissingTextAttribute(map);
|
||||
}
|
||||
|
||||
private void assertMissingTextAttribute(Map<String, Object> attributes) {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(startsWith("Attributes map"));
|
||||
exception.expectMessage(containsString("returned null for required attribute [value]"));
|
||||
exception.expectMessage(containsString("defined by annotation type [" + Component.class.getName() + "]"));
|
||||
synthesizeAnnotation(map, Component.class, null);
|
||||
exception.expectMessage(containsString("returned null for required attribute [text]"));
|
||||
exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"));
|
||||
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -945,14 +975,14 @@ public class AnnotationUtilsTests {
|
||||
}
|
||||
|
||||
|
||||
@Component(value = "meta1")
|
||||
@Component("meta1")
|
||||
@Order
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@interface Meta1 {
|
||||
}
|
||||
|
||||
@Component(value = "meta2")
|
||||
@Component("meta2")
|
||||
@Transactional(readOnly = true)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Meta2 {
|
||||
@@ -1427,4 +1457,16 @@ public class AnnotationUtilsTests {
|
||||
static class ComponentScanClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AnnotationWithDefaults {
|
||||
String text() default "enigma";
|
||||
boolean predicate() default true;
|
||||
char[] characters() default {'a', 'b', 'c'};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AnnotationWithoutDefaults {
|
||||
String text();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user