diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java b/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java index 8c33d8cbf1..2ac61d6167 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java @@ -65,18 +65,18 @@ import java.lang.annotation.Target; *
  • Aliased attributes must declare the same return type.
  • *
  • Aliased attributes must declare a default value.
  • *
  • Aliased attributes must declare the same default value.
  • - *
  • The {@link #annotation} attribute may remain set to the default, - * although setting it to the declaring class for both attributes in the - * pair is also valid.
  • + *
  • The {@link #annotation} attribute should remain set to the default.
  • * * *
  • Alias for attribute in meta-annotation: *
      *
    1. The attribute that is an alias for an attribute in a meta-annotation - * must be annotated with {@code @AliasFor}; the {@link #attribute} must - * reference the aliased attribute in the meta-annotation; and the - * {@link #annotation} must reference the meta-annotation.
    2. + * must be annotated with {@code @AliasFor}, and the {@link #attribute} must + * reference the aliased attribute in the meta-annotation. *
    3. Aliased attributes must declare the same return type.
    4. + *
    5. The {@link #annotation} must reference the meta-annotation.
    6. + *
    7. The referenced meta-annotation must be meta-present on the + * annotation class that declares {@code @AliasFor}.
    8. *
    *
  • * 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 e8fef86db6..606f76feba 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 @@ -1451,6 +1451,7 @@ public abstract class AnnotationUtils { Assert.isTrue(!Annotation.class.equals(targetAnnotationType), "targetAnnotationType must not be java.lang.annotation.Annotation"); + String attributeName = attribute.getName(); AliasFor aliasFor = attribute.getAnnotation(AliasFor.class); // Nothing to check @@ -1470,12 +1471,6 @@ public abstract class AnnotationUtils { return null; } - // Wrong search scope? - if (searchWithinSameAnnotation && !sameTargetDeclared) { - return null; - } - - String attributeName = attribute.getName(); String aliasedAttributeName = getAliasedAttributeName(aliasFor, attribute); if (!StringUtils.hasText(aliasedAttributeName)) { @@ -1485,10 +1480,25 @@ public abstract class AnnotationUtils { throw new AnnotationConfigurationException(msg); } - if (sameTargetDeclared) { + if (!sameTargetDeclared) { + // Target annotation is not meta-present? + if (findAnnotation(sourceAnnotationType, aliasedAnnotationType) == null) { + String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " + + "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.", + attributeName, sourceAnnotationType.getName(), aliasedAttributeName, + aliasedAnnotationType.getName()); + throw new AnnotationConfigurationException(msg); + } + } + else { aliasedAnnotationType = sourceAnnotationType; } + // Wrong search scope? + if (searchWithinSameAnnotation && !sameTargetDeclared) { + return null; + } + Method aliasedAttribute; try { aliasedAttribute = aliasedAnnotationType.getDeclaredMethod(aliasedAttributeName); 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 cd38b6b015..9ff5025581 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 @@ -831,6 +831,18 @@ public class AnnotationUtilsTests { synthesizeAnnotation(annotation); } + @Test + public void synthesizeAnnotationWithAttributeAliasForMetaAnnotationThatIsNotMetaPresent() throws Exception { + AliasedComposedContextConfigNotMetaPresent annotation = AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class); + exception.expect(AnnotationConfigurationException.class); + exception.expectMessage(startsWith("@AliasFor declaration on attribute [xmlConfigFile] in annotation")); + exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName())); + exception.expectMessage(containsString("declares an alias for attribute [locations] in meta-annotation")); + exception.expectMessage(containsString(ContextConfig.class.getName())); + exception.expectMessage(endsWith("which is not meta-present.")); + synthesizeAnnotation(annotation); + } + @Test public void synthesizeAnnotationWithAttributeAliases() throws Exception { Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); @@ -1661,6 +1673,18 @@ public class AnnotationUtilsTests { static class AliasForAttributeWithDifferentDefaultValueClass { } + // @ContextConfig --> Intentionally NOT meta-present + @Retention(RetentionPolicy.RUNTIME) + @interface AliasedComposedContextConfigNotMetaPresent { + + @AliasFor(annotation = ContextConfig.class, attribute = "locations") + String xmlConfigFile(); + } + + @AliasedComposedContextConfigNotMetaPresent(xmlConfigFile = "test.xml") + static class AliasedComposedContextConfigNotMetaPresentClass { + } + @ContextConfig @Retention(RetentionPolicy.RUNTIME) @interface AliasedComposedContextConfig {