Skip to content

Commit 7252920

Browse files
committed
Introduce 'value' alias for 'attribute' in @AliasFor
SPR-11512 introduced support for annotation attribute aliases via @AliasFor, requiring the explicit declaration of the 'attribute' attribute. However, for aliases within an annotation, this explicit declaration is unnecessary. This commit improves the readability of alias pairs declared within an annotation by introducing a 'value' attribute in @AliasFor that is an alias for the existing 'attribute' attribute. This allows annotations such as @ContextConfiguration from the spring-test module to declare aliases as follows. public @interface ContextConfiguration { @AliasFor("locations") String[] value() default {}; @AliasFor("value") String[] locations() default {}; // ... } Issue: SPR-13289
1 parent 90493f4 commit 7252920

File tree

35 files changed

+169
-83
lines changed

35 files changed

+169
-83
lines changed

spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
/**
4545
* Alias for {@link #cacheNames}.
4646
*/
47-
@AliasFor(attribute = "cacheNames")
47+
@AliasFor("cacheNames")
4848
String[] value() default {};
4949

5050
/**
@@ -55,7 +55,7 @@
5555
* @see #value
5656
* @see CacheConfig#cacheNames
5757
*/
58-
@AliasFor(attribute = "value")
58+
@AliasFor("value")
5959
String[] cacheNames() default {};
6060

6161
/**

spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
/**
5151
* Alias for {@link #cacheNames}.
5252
*/
53-
@AliasFor(attribute = "cacheNames")
53+
@AliasFor("cacheNames")
5454
String[] value() default {};
5555

5656
/**
@@ -61,7 +61,7 @@
6161
* @see #value
6262
* @see CacheConfig#cacheNames
6363
*/
64-
@AliasFor(attribute = "value")
64+
@AliasFor("value")
6565
String[] cacheNames() default {};
6666

6767
/**

spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
/**
5656
* Alias for {@link #cacheNames}.
5757
*/
58-
@AliasFor(attribute = "cacheNames")
58+
@AliasFor("cacheNames")
5959
String[] value() default {};
6060

6161
/**
@@ -66,7 +66,7 @@
6666
* @see #value
6767
* @see CacheConfig#cacheNames
6868
*/
69-
@AliasFor(attribute = "value")
69+
@AliasFor("value")
7070
String[] cacheNames() default {};
7171

7272
/**

spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
6363
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
6464
*/
65-
@AliasFor(attribute = "basePackages")
65+
@AliasFor("basePackages")
6666
String[] value() default {};
6767

6868
/**
@@ -72,7 +72,7 @@
7272
* <p>Use {@link #basePackageClasses} for a type-safe alternative to
7373
* String-based package names.
7474
*/
75-
@AliasFor(attribute = "value")
75+
@AliasFor("value")
7676
String[] basePackages() default {};
7777

7878
/**
@@ -166,7 +166,7 @@
166166
* Alias for {@link #classes}.
167167
* @see #classes
168168
*/
169-
@AliasFor(attribute = "classes")
169+
@AliasFor("classes")
170170
Class<?>[] value() default {};
171171

172172
/**
@@ -190,7 +190,7 @@
190190
* @see #value
191191
* @see #type
192192
*/
193-
@AliasFor(attribute = "value")
193+
@AliasFor("value")
194194
Class<?>[] classes() default {};
195195

196196
/**

spring-context/src/main/java/org/springframework/context/annotation/ImportResource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
* @see #locations
6060
* @see #reader
6161
*/
62-
@AliasFor(attribute = "locations")
62+
@AliasFor("locations")
6363
String[] value() default {};
6464

6565
/**
@@ -72,7 +72,7 @@
7272
* @see #value
7373
* @see #reader
7474
*/
75-
@AliasFor(attribute = "value")
75+
@AliasFor("value")
7676
String[] locations() default {};
7777

7878
/**

spring-context/src/main/java/org/springframework/context/annotation/Scope.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
* Alias for {@link #scopeName}.
6262
* @see #scopeName
6363
*/
64-
@AliasFor(attribute = "scopeName")
64+
@AliasFor("scopeName")
6565
String value() default "";
6666

6767
/**
@@ -75,7 +75,7 @@
7575
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
7676
* @see #value
7777
*/
78-
@AliasFor(attribute = "value")
78+
@AliasFor("value")
7979
String scopeName() default "";
8080

8181
/**

spring-context/src/main/java/org/springframework/context/event/EventListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
/**
7070
* Alias for {@link #classes}.
7171
*/
72-
@AliasFor(attribute = "classes")
72+
@AliasFor("classes")
7373
Class<?>[] value() default {};
7474

7575
/**
@@ -79,7 +79,7 @@
7979
* attribute is specified with multiple values, the annotated method
8080
* must <em>not</em> declare any parameters.
8181
*/
82-
@AliasFor(attribute = "value")
82+
@AliasFor("value")
8383
Class<?>[] classes() default {};
8484

8585
/**

spring-context/src/main/java/org/springframework/jmx/export/annotation/ManagedResource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@
4949
/**
5050
* Alias for the {@link #objectName} attribute, for simple default usage.
5151
*/
52-
@AliasFor(attribute = "objectName")
52+
@AliasFor("objectName")
5353
String value() default "";
5454

55-
@AliasFor(attribute = "value")
55+
@AliasFor("value")
5656
String objectName() default "";
5757

5858
String description() default "";

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,22 @@
119119
@Documented
120120
public @interface AliasFor {
121121

122+
/**
123+
* Alias for {@link #attribute}.
124+
* <p>Intended to be used instead of {@link #attribute} when {@link #annotation}
125+
* is not declared &mdash; for example: {@code @AliasFor("value")} instead of
126+
* {@code @AliasFor(attribute = "value")}.
127+
*/
128+
@AliasFor("attribute")
129+
String value() default "";
130+
122131
/**
123132
* The name of the attribute that <em>this</em> attribute is an alias for.
133+
* @see #value
124134
*/
125-
String attribute();
135+
@AliasFor("value")
136+
String attribute() default "";
137+
126138

127139
/**
128140
* The type of annotation in which the aliased {@link #attribute} is declared.

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

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,7 +1427,7 @@ else if (Annotation.class.isAssignableFrom(returnType)) {
14271427
* @see #getAliasedAttributeName(Method, Class)
14281428
*/
14291429
static String getAliasedAttributeName(Method attribute) {
1430-
return getAliasedAttributeName(attribute, null);
1430+
return getAliasedAttributeName(attribute, (Class<? extends Annotation>) null);
14311431
}
14321432

14331433
/**
@@ -1471,7 +1471,7 @@ static String getAliasedAttributeName(Method attribute, Class<? extends Annotati
14711471
}
14721472

14731473
String attributeName = attribute.getName();
1474-
String aliasedAttributeName = aliasFor.attribute();
1474+
String aliasedAttributeName = getAliasedAttributeName(aliasFor, attribute);
14751475

14761476
if (!StringUtils.hasText(aliasedAttributeName)) {
14771477
String msg = String.format(
@@ -1503,7 +1503,7 @@ static String getAliasedAttributeName(Method attribute, Class<? extends Annotati
15031503
throw new AnnotationConfigurationException(msg);
15041504
}
15051505

1506-
String mirrorAliasedAttributeName = mirrorAliasFor.attribute();
1506+
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, aliasedAttribute);
15071507
if (!attributeName.equals(mirrorAliasedAttributeName)) {
15081508
String msg = String.format(
15091509
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
@@ -1543,6 +1543,38 @@ static String getAliasedAttributeName(Method attribute, Class<? extends Annotati
15431543
return aliasedAttributeName;
15441544
}
15451545

1546+
/**
1547+
* Get the name of the aliased attribute configured via the supplied
1548+
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
1549+
* <p>This method returns the value of either the {@code attribute}
1550+
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
1551+
* one of the attributes has been declared.
1552+
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
1553+
* the aliased attribute name
1554+
* @param attribute the attribute that is annotated with {@code @AliasFor},
1555+
* used solely for building an exception message
1556+
* @return the name of the aliased attribute, potentially an empty string
1557+
* @throws AnnotationConfigurationException if invalid configuration of
1558+
* {@code @AliasFor} is detected
1559+
* @since 4.2
1560+
* @see #getAliasedAttributeName(Method, Class)
1561+
*/
1562+
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
1563+
String attributeName = aliasFor.attribute();
1564+
String value = aliasFor.value();
1565+
boolean attributeDeclared = StringUtils.hasText(attributeName);
1566+
boolean valueDeclared = StringUtils.hasText(value);
1567+
1568+
if (attributeDeclared && valueDeclared) {
1569+
throw new AnnotationConfigurationException(String.format(
1570+
"In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' and its alias 'value' "
1571+
+ "are present with values of [%s] and [%s], but only one is permitted.",
1572+
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value));
1573+
}
1574+
1575+
return (attributeDeclared ? attributeName : value);
1576+
}
1577+
15461578
/**
15471579
* Get all methods declared in the supplied {@code annotationType} that
15481580
* match Java's requirements for annotation <em>attributes</em>.

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

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -714,10 +714,30 @@ public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
714714
assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value());
715715
}
716716

717+
@Test
718+
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
719+
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
720+
exception.expect(AnnotationConfigurationException.class);
721+
exception.expectMessage(containsString("@AliasFor declaration on attribute [foo] in annotation"));
722+
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
723+
exception.expectMessage(containsString("is missing required 'attribute' value"));
724+
synthesizeAnnotation(annotation);
725+
}
726+
727+
@Test
728+
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
729+
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
730+
exception.expect(AnnotationConfigurationException.class);
731+
exception.expectMessage(containsString("In @AliasFor declared on attribute [foo] in annotation"));
732+
exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName()));
733+
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
734+
exception.expectMessage(containsString("but only one is permitted"));
735+
synthesizeAnnotation(annotation);
736+
}
737+
717738
@Test
718739
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
719-
AliasForNonexistentAttribute annotation =
720-
AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
740+
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
721741
exception.expect(AnnotationConfigurationException.class);
722742
exception.expectMessage(containsString("Attribute [foo] in"));
723743
exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName()));
@@ -1434,7 +1454,7 @@ enum RequestMethod {
14341454

14351455
String name();
14361456

1437-
@AliasFor(attribute = "path")
1457+
@AliasFor("path")
14381458
String value() default "";
14391459

14401460
@AliasFor(attribute = "value")
@@ -1472,21 +1492,21 @@ public void handleMappedWithDifferentPathAndValueAttributes() {
14721492
@Retention(RetentionPolicy.RUNTIME)
14731493
@interface ContextConfig {
14741494

1475-
@AliasFor(attribute = "locations")
1495+
@AliasFor("locations")
14761496
String value() default "";
14771497

1478-
@AliasFor(attribute = "value")
1498+
@AliasFor("value")
14791499
String locations() default "";
14801500
}
14811501

14821502
@Retention(RetentionPolicy.RUNTIME)
14831503
@interface BrokenContextConfig {
14841504

14851505
// Intentionally missing:
1486-
// @AliasFor(attribute = "locations")
1506+
// @AliasFor("locations")
14871507
String value() default "";
14881508

1489-
@AliasFor(attribute = "value")
1509+
@AliasFor("value")
14901510
String locations() default "";
14911511
}
14921512

@@ -1530,10 +1550,32 @@ static class GroupOfCharsClass {
15301550
}
15311551

15321552

1553+
@Retention(RetentionPolicy.RUNTIME)
1554+
@interface AliasForWithMissingAttributeDeclaration {
1555+
1556+
@AliasFor
1557+
String foo() default "";
1558+
}
1559+
1560+
@AliasForWithMissingAttributeDeclaration
1561+
static class AliasForWithMissingAttributeDeclarationClass {
1562+
}
1563+
1564+
@Retention(RetentionPolicy.RUNTIME)
1565+
@interface AliasForWithDuplicateAttributeDeclaration {
1566+
1567+
@AliasFor(value = "bar", attribute = "baz")
1568+
String foo() default "";
1569+
}
1570+
1571+
@AliasForWithDuplicateAttributeDeclaration
1572+
static class AliasForWithDuplicateAttributeDeclarationClass {
1573+
}
1574+
15331575
@Retention(RetentionPolicy.RUNTIME)
15341576
@interface AliasForNonexistentAttribute {
15351577

1536-
@AliasFor(attribute = "bar")
1578+
@AliasFor("bar")
15371579
String foo() default "";
15381580
}
15391581

@@ -1544,7 +1586,7 @@ static class AliasForNonexistentAttributeClass {
15441586
@Retention(RetentionPolicy.RUNTIME)
15451587
@interface AliasForWithoutMirroredAliasFor {
15461588

1547-
@AliasFor(attribute = "bar")
1589+
@AliasFor("bar")
15481590
String foo() default "";
15491591

15501592
String bar() default "";
@@ -1571,10 +1613,10 @@ static class AliasForWithMirroredAliasForWrongAttributeClass {
15711613
@Retention(RetentionPolicy.RUNTIME)
15721614
@interface AliasForAttributeOfDifferentType {
15731615

1574-
@AliasFor(attribute = "bar")
1616+
@AliasFor("bar")
15751617
String[] foo() default "";
15761618

1577-
@AliasFor(attribute = "foo")
1619+
@AliasFor("foo")
15781620
boolean bar() default true;
15791621
}
15801622

@@ -1599,10 +1641,10 @@ static class AliasForWithMissingDefaultValuesClass {
15991641
@Retention(RetentionPolicy.RUNTIME)
16001642
@interface AliasForAttributeWithDifferentDefaultValue {
16011643

1602-
@AliasFor(attribute = "bar")
1644+
@AliasFor("bar")
16031645
String foo() default "X";
16041646

1605-
@AliasFor(attribute = "foo")
1647+
@AliasFor("foo")
16061648
String bar() default "Z";
16071649
}
16081650

spring-core/src/test/java/org/springframework/core/annotation/subpackage/NonPublicAliasedAnnotation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232

3333
String name();
3434

35-
@AliasFor(attribute = "path")
35+
@AliasFor("path")
3636
String value() default "";
3737

38-
@AliasFor(attribute = "value")
38+
@AliasFor("value")
3939
String path() default "";
4040
}

0 commit comments

Comments
 (0)