From c8d604bf053c6359a3a703f2688daf433494b16c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 6 Aug 2015 19:04:08 +0200 Subject: [PATCH] Ensure @AliasFor overrides attribute in correct meta-annotation Prior to this commit, an explicit override for an attribute in a meta-annotation configured via @AliasFor could potentially result in an incorrect override of an attribute of the same name but in the wrong meta-annotation. This commit fixes the algorithm in getAliasedAttributeName(Method, Class) in AnnotationUtils by ensuring that an explicit attribute override is only applied to the configured target meta-annotation (i.e., configured via the 'annotation' attribute in @AliasFor). Issue: SPR-13325 --- .../core/annotation/AnnotationUtils.java | 5 ++ .../AnnotatedElementUtilsTests.java | 50 +++++++++++++++++++ .../core/annotation/AnnotationUtilsTests.java | 7 +++ 3 files changed, 62 insertions(+) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 74fac9d4366..e8fef86db68 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1465,6 +1465,11 @@ public abstract class AnnotationUtils { boolean sameTargetDeclared = (sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType)); + // Explicit alias for a different target meta-annotation? + if (!searchWithinSameAnnotation && !targetAnnotationType.equals(aliasedAnnotationType)) { + return null; + } + // Wrong search scope? if (searchWithinSameAnnotation && !sameTargetDeclared) { return null; diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 7d7a6c1b0cd..958173b3655 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -491,6 +491,30 @@ public class AnnotatedElementUtilsTests { assertEquals("TX qualifier via synthesized annotation.", "aliasForQualifier", annotation.qualifier()); } + @Test + public void findMergedAnnotationForMultipleMetaAnnotationsWithClashingAttributeNames() { + final String[] xmlLocations = new String[] { "test.xml" }; + final String[] propFiles = new String[] { "test.properties" }; + + Class element = AliasedComposedContextConfigAndTestPropSourceClass.class; + + ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class); + assertNotNull("@ContextConfig on " + element, contextConfig); + assertArrayEquals("locations", xmlLocations, contextConfig.locations()); + assertArrayEquals("value", xmlLocations, contextConfig.value()); + + // Synthesized annotation + TestPropSource testPropSource = AnnotationUtils.findAnnotation(element, TestPropSource.class); + assertArrayEquals("locations", propFiles, testPropSource.locations()); + assertArrayEquals("value", propFiles, testPropSource.value()); + + // Merged annotation + testPropSource = findMergedAnnotation(element, TestPropSource.class); + assertNotNull("@TestPropSource on " + element, testPropSource); + assertArrayEquals("locations", propFiles, testPropSource.locations()); + assertArrayEquals("value", propFiles, testPropSource.value()); + } + @Test public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() { Class element = TestComponentScanClass.class; @@ -622,6 +646,19 @@ public class AnnotatedElementUtilsTests { @interface MetaAndLocalTxConfig { } + /** + * Mock of {@code org.springframework.test.context.TestPropertySource}. + */ + @Retention(RetentionPolicy.RUNTIME) + @interface TestPropSource { + + @AliasFor("locations") + String[] value() default {}; + + @AliasFor("value") + String[] locations() default {}; + } + /** * Mock of {@code org.springframework.test.context.ContextConfiguration}. */ @@ -679,6 +716,15 @@ public class AnnotatedElementUtilsTests { String[] xmlConfigFiles(); } + @ContextConfig(locations = "shadowed.xml") + @TestPropSource(locations = "test.properties") + @Retention(RetentionPolicy.RUNTIME) + @interface AliasedComposedContextConfigAndTestPropSource { + + @AliasFor(annotation = ContextConfig.class, attribute = "locations") + String[] xmlConfigFiles() default "default.xml"; + } + /** * Mock of {@code org.springframework.context.annotation.ComponentScan} */ @@ -855,6 +901,10 @@ public class AnnotatedElementUtilsTests { static class InvalidAliasedComposedContextConfigClass { } + @AliasedComposedContextConfigAndTestPropSource(xmlConfigFiles = "test.xml") + static class AliasedComposedContextConfigAndTestPropSourceClass { + } + @TestComponentScan(packages = "com.example.app.test") static class TestComponentScanClass { } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 6a3b3a024e0..08519ece3de 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -675,6 +675,13 @@ public class AnnotationUtilsTests { assertThat(set.size(), is(0)); } + @Test + public void getAliasedAttributeNameFromWrongTargetAnnotation() throws Exception { + Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile"); + assertNull("xmlConfigFile is not an alias for @Component.", + getAliasedAttributeName(attribute, Component.class)); + } + @Test public void getAliasedAttributeNameFromAliasedComposedAnnotation() throws Exception { Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");