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:
*
* - 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.
+ * must be annotated with {@code @AliasFor}, and the {@link #attribute} must
+ * reference the aliased attribute in the meta-annotation.
* Aliased attributes must declare the same return type.
+ * The {@link #annotation} must reference the meta-annotation.
+ * The referenced meta-annotation must be meta-present on the
+ * annotation class that declares {@code @AliasFor}.
*
*
*
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 {