Browse Source

Ensure synthesized nested annotation arrays retain correct type

Prior to this commit, when a nested array of annotations was
synthesized while adapting values within an AnnotationAttributes map,
the array was improperly replaced with an array of type Annotation[]
instead of an array of the concrete annotation type, which can lead to
unexpected run-time exceptions.

This commit fixes this bug by replacing annotations in the existing
array with synthesized versions of those annotations, thereby retaining
the original array's component type.

Issue: SPR-13077
pull/808/head
Sam Brannen 10 years ago
parent
commit
f41de12cf6
  1. 5
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  2. 54
      spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
  3. 36
      spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

5
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

@ -924,11 +924,10 @@ public abstract class AnnotationUtils { @@ -924,11 +924,10 @@ public abstract class AnnotationUtils {
return mappedAnnotations;
}
else {
Annotation[] synthesizedAnnotations = new Annotation[annotations.length];
for (int i = 0; i < annotations.length; i++) {
synthesizedAnnotations[i] = synthesizeAnnotation(annotations[i], annotatedElement);
annotations[i] = synthesizeAnnotation(annotations[i], annotatedElement);
}
return synthesizedAnnotations;
return annotations;
}
}

54
spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

@ -22,8 +22,8 @@ import java.lang.annotation.Retention; @@ -22,8 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
@ -32,9 +32,9 @@ import org.junit.rules.ExpectedException; @@ -32,9 +32,9 @@ import org.junit.rules.ExpectedException;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.Matchers.*;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
@ -460,8 +460,23 @@ public class AnnotatedElementUtilsTests { @@ -460,8 +460,23 @@ public class AnnotatedElementUtilsTests {
attributes.getString("qualifier"));
}
@Test
public void findAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
Class<?> element = TestComponentScanClass.class;
AnnotationAttributes attributes = findAnnotationAttributes(element, ComponentScan.class);
assertNotNull("Should find @ComponentScan on " + element, attributes);
assertArrayEquals("basePackages for " + element, new String[] { "com.example.app.test" },
attributes.getStringArray("basePackages"));
Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
assertNotNull(excludeFilters);
List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList());
assertEquals(asList("*Test", "*Tests"), patterns);
}
private Set<String> names(Class<?>... classes) {
return stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet());
return stream(classes).map(Class::getName).collect(toSet());
}
@ -627,6 +642,32 @@ public class AnnotatedElementUtilsTests { @@ -627,6 +642,32 @@ public class AnnotatedElementUtilsTests {
String[] xmlConfigFiles();
}
/**
* Mock of {@code org.springframework.context.annotation.ComponentScan}
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScan {
String[] basePackages() default {};
Filter[] excludeFilters() default {};
}
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
String pattern();
}
@ComponentScan(excludeFilters = { @Filter(pattern = "*Test"), @Filter(pattern = "*Tests") })
@Retention(RetentionPolicy.RUNTIME)
@interface TestComponentScan {
@AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
String[] packages();
}
// -------------------------------------------------------------------------
static class NonAnnotatedClass {
@ -776,4 +817,9 @@ public class AnnotatedElementUtilsTests { @@ -776,4 +817,9 @@ public class AnnotatedElementUtilsTests {
@InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
static class InvalidAliasedComposedContextConfigClass {
}
@TestComponentScan(packages = "com.example.app.test")
static class TestComponentScanClass {
}
}

36
spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

@ -21,6 +21,7 @@ import java.lang.annotation.Inherited; @@ -21,6 +21,7 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
@ -35,6 +36,7 @@ import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass; @@ -35,6 +36,7 @@ import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -393,6 +395,22 @@ public class AnnotationUtilsTests { @@ -393,6 +395,22 @@ public class AnnotationUtilsTests {
assertEquals(Component.class, attributes.annotationType());
}
@Test
public void getAnnotationAttributesWithNestedAnnotations() {
ComponentScan componentScan = ComponentScanClass.class.getAnnotation(ComponentScan.class);
assertNotNull(componentScan);
AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanClass.class, componentScan);
assertNotNull(attributes);
assertEquals(ComponentScan.class, attributes.annotationType());
Filter[] filters = attributes.getAnnotationArray("excludeFilters", Filter.class);
assertNotNull(filters);
List<String> patterns = stream(filters).map(Filter::pattern).collect(toList());
assertEquals(asList("*Foo", "*Bar"), patterns);
}
@Test
public void getAnnotationAttributesWithAttributeAliases() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
@ -1222,4 +1240,22 @@ public class AnnotationUtilsTests { @@ -1222,4 +1240,22 @@ public class AnnotationUtilsTests {
String xmlConfigFile();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
String pattern();
}
/**
* Mock of {@code org.springframework.context.annotation.ComponentScan}
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScan {
Filter[] excludeFilters() default {};
}
@ComponentScan(excludeFilters = { @Filter(pattern = "*Foo"), @Filter(pattern = "*Bar") })
static class ComponentScanClass {
}
}

Loading…
Cancel
Save