Skip to content

Commit 3242ad8

Browse files
committed
@AliasFor attribute name defaults to declaring attribute
Issue: SPR-13828
1 parent e48ec4f commit 3242ad8

File tree

2 files changed

+49
-50
lines changed

2 files changed

+49
-50
lines changed

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

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,11 +1945,18 @@ private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
19451945

19461946
this.sourceAttribute = sourceAttribute;
19471947
this.sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
1948-
this.sourceAttributeName = this.sourceAttribute.getName();
1948+
this.sourceAttributeName = sourceAttribute.getName();
19491949

19501950
this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ?
19511951
this.sourceAnnotationType : aliasFor.annotation());
1952-
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, this.sourceAttribute);
1952+
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
1953+
if (this.aliasedAnnotationType == this.sourceAnnotationType &&
1954+
this.aliasedAttributeName.equals(this.sourceAttributeName)) {
1955+
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] points to " +
1956+
"itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
1957+
sourceAttribute.getName(), declaringClass.getName());
1958+
throw new AnnotationConfigurationException(msg);
1959+
}
19531960
try {
19541961
this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
19551962
}
@@ -2124,30 +2131,23 @@ private AliasDescriptor getAttributeOverrideDescriptor() {
21242131
return AliasDescriptor.from(this.aliasedAttribute);
21252132
}
21262133

2127-
@Override
2128-
public String toString() {
2129-
return String.format("%s: @%s(%s) is an alias for @%s(%s)", getClass().getSimpleName(),
2130-
this.sourceAnnotationType.getSimpleName(), this.sourceAttributeName,
2131-
this.aliasedAnnotationType.getSimpleName(), this.aliasedAttributeName);
2132-
}
2133-
21342134
/**
21352135
* Get the name of the aliased attribute configured via the supplied
2136-
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
2136+
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute},
2137+
* or the original attribute if no aliased one specified (indicating that
2138+
* the reference goes to a same-named attribute on a meta-annotation).
21372139
* <p>This method returns the value of either the {@code attribute}
21382140
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
21392141
* one of the attributes has been declared while simultaneously ensuring
21402142
* that at least one of the attributes has been declared.
21412143
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
21422144
* the aliased attribute name
2143-
* @param attribute the attribute that is annotated with {@code @AliasFor},
2144-
* used solely for building an exception message
2145+
* @param attribute the attribute that is annotated with {@code @AliasFor}
21452146
* @return the name of the aliased attribute (never {@code null} or empty)
21462147
* @throws AnnotationConfigurationException if invalid configuration of
21472148
* {@code @AliasFor} is detected
2148-
* @since 4.2
21492149
*/
2150-
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
2150+
private String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
21512151
String attributeName = aliasFor.attribute();
21522152
String value = aliasFor.value();
21532153
boolean attributeDeclared = StringUtils.hasText(attributeName);
@@ -2156,22 +2156,20 @@ private static String getAliasedAttributeName(AliasFor aliasFor, Method attribut
21562156
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
21572157
if (attributeDeclared && valueDeclared) {
21582158
throw new AnnotationConfigurationException(String.format(
2159-
"In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' and its " +
2160-
"alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
2161-
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value));
2159+
"In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' and its " +
2160+
"alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
2161+
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value));
21622162
}
21632163

21642164
attributeName = (attributeDeclared ? attributeName : value);
2165+
return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName());
2166+
}
21652167

2166-
// Ensure user declared either 'value' or 'attribute' in @AliasFor
2167-
if (!StringUtils.hasText(attributeName)) {
2168-
String msg = String.format(
2169-
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
2170-
attribute.getName(), attribute.getDeclaringClass().getName());
2171-
throw new AnnotationConfigurationException(msg);
2172-
}
2173-
2174-
return attributeName.trim();
2168+
@Override
2169+
public String toString() {
2170+
return String.format("%s: @%s(%s) is an alias for @%s(%s)", getClass().getSimpleName(),
2171+
this.sourceAnnotationType.getSimpleName(), this.sourceAttributeName,
2172+
this.aliasedAnnotationType.getSimpleName(), this.aliasedAttributeName);
21752173
}
21762174
}
21772175

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

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public void findAnnotationDeclaringClassForAllScenarios() throws Exception {
330330
@Test
331331
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
332332
// no class-level annotation
333-
List<Class<? extends Annotation>> transactionalCandidateList = asList(Transactional.class);
333+
List<Class<? extends Annotation>> transactionalCandidateList = Collections.singletonList(Transactional.class);
334334
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
335335
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
336336

@@ -345,7 +345,7 @@ public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
345345

346346
// non-inherited class-level annotation; note: @Order is not inherited,
347347
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
348-
List<Class<? extends Annotation>> orderCandidateList = asList(Order.class);
348+
List<Class<? extends Annotation>> orderCandidateList = Collections.singletonList(Order.class);
349349
assertEquals(NonInheritedAnnotationInterface.class,
350350
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
351351
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
@@ -478,12 +478,12 @@ public void getAnnotationAttributesWithAttributeAliases() throws Exception {
478478
assertEquals("value attribute: ", "/test", attributes.getString(VALUE));
479479
assertEquals("path attribute: ", "/test", attributes.getString("path"));
480480

481-
method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
482-
webMapping = method.getAnnotation(WebMapping.class);
483481
exception.expect(AnnotationConfigurationException.class);
484482
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
485483
exception.expectMessage(containsString("values of [/enigma] and [/test]"));
486-
exception.expectMessage(endsWith("but only one is permitted."));
484+
485+
method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
486+
webMapping = method.getAnnotation(WebMapping.class);
487487
getAnnotationAttributes(webMapping);
488488
}
489489

@@ -554,7 +554,8 @@ public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDecl
554554
exception.expect(AnnotationConfigurationException.class);
555555
exception.expectMessage(startsWith("Attribute [value] in"));
556556
exception.expectMessage(containsString(BrokenContextConfig.class.getName()));
557-
exception.expectMessage(endsWith("must be declared as an @AliasFor [location]."));
557+
exception.expectMessage(containsString("@AliasFor [location]"));
558+
558559
getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class);
559560
}
560561

@@ -845,7 +846,7 @@ public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() thr
845846
exception.expect(AnnotationConfigurationException.class);
846847
exception.expectMessage(startsWith("@AliasFor declaration on attribute [foo] in annotation"));
847848
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
848-
exception.expectMessage(endsWith("is missing required 'attribute' value."));
849+
exception.expectMessage(containsString("points to itself"));
849850
synthesizeAnnotation(annotation);
850851
}
851852

@@ -856,7 +857,6 @@ public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration()
856857
exception.expectMessage(startsWith("In @AliasFor declared on attribute [foo] in annotation"));
857858
exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName()));
858859
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
859-
exception.expectMessage(endsWith("but only one is permitted."));
860860
synthesizeAnnotation(annotation);
861861
}
862862

@@ -877,7 +877,7 @@ public void synthesizeAnnotationWithAttributeAliasWithoutMirroredAliasFor() thro
877877
exception.expect(AnnotationConfigurationException.class);
878878
exception.expectMessage(startsWith("Attribute [bar] in"));
879879
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
880-
exception.expectMessage(endsWith("must be declared as an @AliasFor [foo]."));
880+
exception.expectMessage(containsString("@AliasFor [foo]"));
881881
synthesizeAnnotation(annotation);
882882
}
883883

@@ -903,7 +903,7 @@ public void synthesizeAnnotationWithAttributeAliasForAttributeOfDifferentType()
903903
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
904904
exception.expectMessage(containsString("attribute [foo]"));
905905
exception.expectMessage(containsString("attribute [bar]"));
906-
exception.expectMessage(endsWith("must declare the same return type."));
906+
exception.expectMessage(containsString("same return type"));
907907
synthesizeAnnotation(annotation);
908908
}
909909

@@ -916,7 +916,7 @@ public void synthesizeAnnotationWithAttributeAliasForWithMissingDefaultValues()
916916
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
917917
exception.expectMessage(containsString("attribute [foo] in annotation"));
918918
exception.expectMessage(containsString("attribute [bar] in annotation"));
919-
exception.expectMessage(endsWith("must declare default values."));
919+
exception.expectMessage(containsString("default values"));
920920
synthesizeAnnotation(annotation);
921921
}
922922

@@ -929,19 +929,20 @@ public void synthesizeAnnotationWithAttributeAliasForAttributeWithDifferentDefau
929929
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
930930
exception.expectMessage(containsString("attribute [foo] in annotation"));
931931
exception.expectMessage(containsString("attribute [bar] in annotation"));
932-
exception.expectMessage(endsWith("must declare the same default value."));
932+
exception.expectMessage(containsString("same default value"));
933933
synthesizeAnnotation(annotation);
934934
}
935935

936936
@Test
937937
public void synthesizeAnnotationWithAttributeAliasForMetaAnnotationThatIsNotMetaPresent() throws Exception {
938-
AliasedComposedContextConfigNotMetaPresent annotation = AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
938+
AliasedComposedContextConfigNotMetaPresent annotation =
939+
AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
939940
exception.expect(AnnotationConfigurationException.class);
940941
exception.expectMessage(startsWith("@AliasFor declaration on attribute [xmlConfigFile] in annotation"));
941942
exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName()));
942943
exception.expectMessage(containsString("declares an alias for attribute [location] in meta-annotation"));
943944
exception.expectMessage(containsString(ContextConfig.class.getName()));
944-
exception.expectMessage(endsWith("which is not meta-present."));
945+
exception.expectMessage(containsString("not meta-present"));
945946
synthesizeAnnotation(annotation);
946947
}
947948

@@ -1039,7 +1040,7 @@ public void synthesizeAnnotationWithImplicitAliasesWithMissingDefaultValues() th
10391040
exception.expectMessage(startsWith("Misconfigured aliases:"));
10401041
exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
10411042
exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
1042-
exception.expectMessage(endsWith("must declare default values."));
1043+
exception.expectMessage(containsString("default values"));
10431044

10441045
synthesizeAnnotation(config, clazz);
10451046
}
@@ -1055,7 +1056,7 @@ public void synthesizeAnnotationWithImplicitAliasesWithDifferentDefaultValues()
10551056
exception.expectMessage(startsWith("Misconfigured aliases:"));
10561057
exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
10571058
exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
1058-
exception.expectMessage(endsWith("must declare the same default value."));
1059+
exception.expectMessage(containsString("same default value"));
10591060

10601061
synthesizeAnnotation(config, clazz);
10611062
}
@@ -1077,10 +1078,9 @@ public void synthesizeAnnotationWithImplicitAliasesWithDuplicateValues() throws
10771078
exception.expectMessage(containsString(clazz.getName()));
10781079
exception.expectMessage(containsString("and synthesized from"));
10791080
exception.expectMessage(either(containsString("attribute 'location1' and its alias 'location2'")).or(
1080-
containsString("attribute 'location2' and its alias 'location1'")));
1081+
containsString("attribute 'location2' and its alias 'location1'")));
10811082
exception.expectMessage(either(containsString("are present with values of [1] and [2]")).or(
1082-
containsString("are present with values of [2] and [1]")));
1083-
exception.expectMessage(endsWith("but only one is permitted."));
1083+
containsString("are present with values of [2] and [1]")));
10841084

10851085
synthesizedConfig.location1();
10861086
}
@@ -1106,8 +1106,8 @@ public void synthesizeAnnotationFromMapWithNestedMap() throws Exception {
11061106
assertNotNull(componentScan);
11071107
assertEquals("value from ComponentScan: ", "*Foo", componentScan.value().pattern());
11081108

1109-
AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanSingleFilterClass.class, componentScan,
1110-
false, true);
1109+
AnnotationAttributes attributes = getAnnotationAttributes(
1110+
ComponentScanSingleFilterClass.class, componentScan, false, true);
11111111
assertNotNull(attributes);
11121112
assertEquals(ComponentScanSingleFilter.class, attributes.annotationType());
11131113

@@ -1119,8 +1119,8 @@ public void synthesizeAnnotationFromMapWithNestedMap() throws Exception {
11191119
filterMap.put("pattern", "newFoo");
11201120
filterMap.put("enigma", 42);
11211121

1122-
ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(attributes,
1123-
ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class);
1122+
ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(
1123+
attributes, ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class);
11241124
assertNotNull(synthesizedComponentScan);
11251125

11261126
assertNotSame(componentScan, synthesizedComponentScan);
@@ -1235,6 +1235,7 @@ public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exc
12351235
exception.expectMessage(containsString("for attribute [value]"));
12361236
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
12371237
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
1238+
12381239
synthesizeAnnotation(map, Component.class, null);
12391240
}
12401241

@@ -1410,7 +1411,7 @@ public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws
14101411
ContextConfig[] configs = synthesizedHierarchy.value();
14111412
assertNotNull(configs);
14121413
assertTrue("nested annotations must be synthesized",
1413-
stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
1414+
stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
14141415

14151416
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
14161417
assertThat(locations, is(expectedLocations));

0 commit comments

Comments
 (0)