Support meta-annotation overrides in ASM processing

Prior to this commit, Spring supported meta-annotation attribute
overrides in custom composed annotations with reflection-based
annotation processing but not with ASM-based annotation processing.

This commit ensures that meta-annotation attribute overrides are
supported in AnnotationMetadataReadingVisitor.getAnnotationAttributes().

Issue: SPR-11574
This commit is contained in:
Sam Brannen
2014-03-19 23:44:13 +01:00
parent 1bab8a3916
commit 99cd2f6098
9 changed files with 242 additions and 146 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,6 @@
package org.springframework.core.type;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
@@ -43,6 +34,9 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
* Unit tests demonstrating that the reflection-based {@link StandardAnnotationMetadata}
* and ASM-based {@code AnnotationMetadataReadingVisitor} produce identical output.
@@ -50,18 +44,19 @@ import org.springframework.stereotype.Component;
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
*/
public class AnnotationMetadataTests {
@Test
public void testStandardAnnotationMetadata() throws IOException {
public void standardAnnotationMetadata() throws Exception {
AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class, true);
doTestAnnotationInfo(metadata);
doTestMethodAnnotationInfo(metadata);
}
@Test
public void testAsmAnnotationMetadata() throws IOException {
public void asmAnnotationMetadata() throws Exception {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(AnnotatedComponent.class.getName());
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
@@ -77,14 +72,43 @@ public class AnnotationMetadataTests {
* 'true' as is done in the main test above.
*/
@Test
public void testStandardAnnotationMetadata_nestedAnnotationsAsMap_false() throws IOException {
public void standardAnnotationMetadata_nestedAnnotationsAsMap_false() throws Exception {
AnnotationMetadata metadata = new StandardAnnotationMetadata(AnnotatedComponent.class);
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
Annotation[] nestedAnnoArray = (Annotation[])specialAttrs.get("nestedAnnoArray");
Annotation[] nestedAnnoArray = (Annotation[]) specialAttrs.get("nestedAnnoArray");
assertThat(nestedAnnoArray[0], instanceOf(NestedAnno.class));
}
@Test
public void metaAnnotationOverridesUsingStandardAnnotationMetadata() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(
ComposedConfigurationWithAttributeOverridesClass.class);
assertMetaAnnotationOverrides(metadata);
}
@Test
public void metaAnnotationOverridesUsingAnnotationMetadataReadingVisitor() throws Exception {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(ComposedConfigurationWithAttributeOverridesClass.class.getName());
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
assertMetaAnnotationOverrides(metadata);
}
private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) {
AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes(
TestComponentScan.class.getName(), false);
String[] basePackages = attributes.getStringArray("basePackages");
assertThat("length of basePackages[]", basePackages.length, is(1));
assertThat("basePackages[0]", basePackages[0], is("org.example.componentscan"));
String[] value = attributes.getStringArray("value");
assertThat("length of value[]", value.length, is(0));
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
assertThat("length of basePackageClasses[]", basePackageClasses.length, is(0));
}
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
assertThat(metadata.isInterface(), is(false));
@@ -127,58 +151,59 @@ public class AnnotationMetadataTests {
AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
assertThat("na", is(nestedAnno.getString("value")));
assertTrue(nestedAnno.getEnum("anEnum").equals(SomeEnum.LABEL1));
assertArrayEquals(new Class[]{String.class}, (Class[])nestedAnno.get("classArray"));
assertArrayEquals(new Class[] { String.class }, (Class[]) nestedAnno.get("classArray"));
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
assertThat(nestedAnnoArray.length, is(2));
assertThat(nestedAnnoArray[0].getString("value"), is("default"));
assertTrue(nestedAnnoArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])nestedAnnoArray[0].get("classArray"));
assertArrayEquals(new Class[] { Void.class }, (Class[]) nestedAnnoArray[0].get("classArray"));
assertThat(nestedAnnoArray[1].getString("value"), is("na1"));
assertTrue(nestedAnnoArray[1].getEnum("anEnum").equals(SomeEnum.LABEL2));
assertArrayEquals(new Class[]{Number.class}, (Class[])nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new Class[]{Number.class}, nestedAnnoArray[1].getClassArray("classArray"));
assertArrayEquals(new Class[] { Number.class }, (Class[]) nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new Class[] { Number.class }, nestedAnnoArray[1].getClassArray("classArray"));
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
assertThat(optional.getString("value"), is("optional"));
assertTrue(optional.getEnum("anEnum").equals(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])optional.get("classArray"));
assertArrayEquals(new Class[]{Void.class}, optional.getClassArray("classArray"));
assertArrayEquals(new Class[] { Void.class }, (Class[]) optional.get("classArray"));
assertArrayEquals(new Class[] { Void.class }, optional.getClassArray("classArray"));
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
assertThat(optionalArray.length, is(1));
assertThat(optionalArray[0].getString("value"), is("optional"));
assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray"));
assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray"));
assertArrayEquals(new Class[] { Void.class }, (Class[]) optionalArray[0].get("classArray"));
assertArrayEquals(new Class[] { Void.class }, optionalArray[0].getClassArray("classArray"));
assertEquals("direct", metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
}
{ // perform tests with classValuesAsString = true
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(
SpecialAttr.class.getName(), true);
assertThat(specialAttrs.size(), is(6));
assertThat(specialAttrs.get("clazz"), is((Object)String.class.getName()));
assertThat(specialAttrs.get("clazz"), is((Object) String.class.getName()));
assertThat(specialAttrs.getString("clazz"), is(String.class.getName()));
AnnotationAttributes nestedAnno = specialAttrs.getAnnotation("nestedAnno");
assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
assertArrayEquals(new String[]{String.class.getName()}, nestedAnno.getStringArray("classArray"));
assertArrayEquals(new String[] { String.class.getName() }, nestedAnno.getStringArray("classArray"));
assertArrayEquals(new String[] { String.class.getName() }, nestedAnno.getStringArray("classArray"));
AnnotationAttributes[] nestedAnnoArray = specialAttrs.getAnnotationArray("nestedAnnoArray");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])nestedAnnoArray[0].get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, nestedAnnoArray[0].getStringArray("classArray"));
assertArrayEquals(new String[]{Number.class.getName()}, (String[])nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new String[]{Number.class.getName()}, nestedAnnoArray[1].getStringArray("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) nestedAnnoArray[0].get("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, nestedAnnoArray[0].getStringArray("classArray"));
assertArrayEquals(new String[] { Number.class.getName() }, (String[]) nestedAnnoArray[1].get("classArray"));
assertArrayEquals(new String[] { Number.class.getName() }, nestedAnnoArray[1].getStringArray("classArray"));
AnnotationAttributes optional = specialAttrs.getAnnotation("optional");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optional.get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, optional.getStringArray("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) optional.get("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, optional.getStringArray("classArray"));
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, (String[]) optionalArray[0].get("classArray"));
assertArrayEquals(new String[] { Void.class.getName() }, optionalArray[0].getStringArray("classArray"));
assertEquals(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"), "direct");
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
@@ -194,6 +219,9 @@ public class AnnotationMetadataTests {
}
}
// -------------------------------------------------------------------------
public static enum SomeEnum {
LABEL1, LABEL2, DEFAULT;
}
@@ -201,8 +229,11 @@ public class AnnotationMetadataTests {
@Target({})
@Retention(RetentionPolicy.RUNTIME)
public @interface NestedAnno {
String value() default "default";
SomeEnum anEnum() default SomeEnum.DEFAULT;
Class<?>[] classArray() default Void.class;
}
@@ -218,14 +249,15 @@ public class AnnotationMetadataTests {
NestedAnno[] nestedAnnoArray();
NestedAnno optional() default @NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class);
NestedAnno optional() default @NestedAnno(value = "optional", anEnum = SomeEnum.DEFAULT, classArray = Void.class);
NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)};
NestedAnno[] optionalArray() default { @NestedAnno(value = "optional", anEnum = SomeEnum.DEFAULT, classArray = Void.class) };
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectAnnotation {
String value();
}
@@ -250,6 +282,7 @@ public class AnnotationMetadataTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumSubclasses {
SubclassEnum[] value();
}
@@ -263,13 +296,9 @@ public class AnnotationMetadataTests {
@Component("myName")
@Scope("myScope")
@SpecialAttr(clazz = String.class, state = Thread.State.NEW,
nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = {String.class}),
nestedAnnoArray = {
@NestedAnno,
@NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class})
})
@SuppressWarnings({"serial", "unused"})
@SpecialAttr(clazz = String.class, state = Thread.State.NEW, nestedAnno = @NestedAnno(value = "na", anEnum = SomeEnum.LABEL1, classArray = { String.class }), nestedAnnoArray = {
@NestedAnno, @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = { Number.class }) })
@SuppressWarnings({ "serial", "unused" })
@DirectAnnotation("direct")
@MetaMetaAnnotation
@EnumSubclasses({ SubclassEnum.FOO, SubclassEnum.BAR })
@@ -279,7 +308,7 @@ public class AnnotationMetadataTests {
public void doWork(@TestQualifier("myColor") java.awt.Color color) {
}
public void doSleep() {
public void doSleep() {
}
@DirectAnnotation("direct")
@@ -288,4 +317,36 @@ public class AnnotationMetadataTests {
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public static @interface TestConfiguration {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface TestComponentScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@TestConfiguration
@TestComponentScan(basePackages = "bogus")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@ComposedConfigurationWithAttributeOverrides(basePackages = "org.example.componentscan")
public static class ComposedConfigurationWithAttributeOverridesClass {
}
}