Skip to content

Commit b7acddb

Browse files
committed
AnnotationAttributes stores and re-throws resolution exceptions
Issue: SPR-13177
1 parent 3f0a6e8 commit b7acddb

File tree

3 files changed

+43
-22
lines changed

3 files changed

+43
-22
lines changed

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public AnnotationAttributes(Map<String, Object> map) {
9898
this.displayName = "unknown";
9999
}
100100

101+
101102
/**
102103
* Get the type of annotation represented by this
103104
* {@code AnnotationAttributes} instance.
@@ -128,7 +129,6 @@ public String getString(String attributeName) {
128129
* <p>If there is no value stored under the specified {@code attributeName}
129130
* but the attribute has an alias declared via {@code @AliasFor}, the
130131
* value of the alias will be returned.
131-
*
132132
* @param attributeName the name of the attribute to get; never
133133
* {@code null} or empty
134134
* @param annotationType the type of annotation represented by this
@@ -146,6 +146,7 @@ public String getString(String attributeName) {
146146
*/
147147
public String getAliasedString(String attributeName, Class<? extends Annotation> annotationType,
148148
Object annotationSource) {
149+
149150
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String.class);
150151
}
151152

@@ -172,7 +173,6 @@ public String[] getStringArray(String attributeName) {
172173
* <p>If there is no value stored under the specified {@code attributeName}
173174
* but the attribute has an alias declared via {@code @AliasFor}, the
174175
* value of the alias will be returned.
175-
*
176176
* @param attributeName the name of the attribute to get; never
177177
* {@code null} or empty
178178
* @param annotationType the type of annotation represented by this
@@ -189,6 +189,7 @@ public String[] getStringArray(String attributeName) {
189189
*/
190190
public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
191191
Object annotationSource) {
192+
192193
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String[].class);
193194
}
194195

@@ -270,7 +271,6 @@ public Class<?>[] getClassArray(String attributeName) {
270271
* <p>If there is no value stored under the specified {@code attributeName}
271272
* but the attribute has an alias declared via {@code @AliasFor}, the
272273
* value of the alias will be returned.
273-
*
274274
* @param attributeName the name of the attribute to get; never
275275
* {@code null} or empty
276276
* @param annotationType the type of annotation represented by this
@@ -287,6 +287,7 @@ public Class<?>[] getClassArray(String attributeName) {
287287
*/
288288
public Class<?>[] getAliasedClassArray(String attributeName, Class<? extends Annotation> annotationType,
289289
Object annotationSource) {
290+
290291
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, Class[].class);
291292
}
292293

@@ -378,8 +379,9 @@ private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType)
378379
Assert.hasText(attributeName, "attributeName must not be null or empty");
379380
Object value = get(attributeName);
380381
assertAttributePresence(attributeName, value);
381-
if (!expectedType.isInstance(value) && expectedType.isArray()
382-
&& expectedType.getComponentType().isInstance(value)) {
382+
assertNotException(attributeName, value);
383+
if (!expectedType.isInstance(value) && expectedType.isArray() &&
384+
expectedType.getComponentType().isInstance(value)) {
383385
Object array = Array.newInstance(expectedType.getComponentType(), 1);
384386
Array.set(array, 0, value);
385387
value = array;
@@ -395,7 +397,6 @@ private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType)
395397
* <p>If there is no value stored under the specified {@code attributeName}
396398
* but the attribute has an alias declared via {@code @AliasFor}, the
397399
* value of the alias will be returned.
398-
*
399400
* @param attributeName the name of the attribute to get; never
400401
* {@code null} or empty
401402
* @param annotationType the type of annotation represented by this
@@ -427,10 +428,10 @@ private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extend
427428

428429
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) && attributeDeclared && aliasDeclared) {
429430
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
430-
String msg = String.format("In annotation [%s] declared on [%s], "
431-
+ "attribute [%s] and its alias [%s] are present with values of [%s] and [%s], "
432-
+ "but only one is permitted.", annotationType.getName(), elementName, attributeName, aliasName,
433-
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
431+
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
432+
"are present with values of [%s] and [%s], but only one is permitted.",
433+
annotationType.getName(), elementName, attributeName, aliasName,
434+
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
434435
throw new AnnotationConfigurationException(msg);
435436
}
436437

@@ -458,6 +459,7 @@ private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extend
458459
private <T> T getAttribute(String attributeName, Class<T> expectedType) {
459460
Object value = get(attributeName);
460461
if (value != null) {
462+
assertNotException(attributeName, value);
461463
assertAttributeType(attributeName, value, expectedType);
462464
}
463465
return (T) value;
@@ -466,24 +468,32 @@ private <T> T getAttribute(String attributeName, Class<T> expectedType) {
466468
private void assertAttributePresence(String attributeName, Object attributeValue) {
467469
if (attributeValue == null) {
468470
throw new IllegalArgumentException(String.format(
469-
"Attribute '%s' not found in attributes for annotation [%s]", attributeName, this.displayName));
471+
"Attribute '%s' not found in attributes for annotation [%s]", attributeName, this.displayName));
470472
}
471473
}
472474

473475
private void assertAttributePresence(String attributeName, String aliasName, Object attributeValue) {
474476
if (attributeValue == null) {
475477
throw new IllegalArgumentException(String.format(
476-
"Neither attribute '%s' nor its alias '%s' was found in attributes for annotation [%s]", attributeName,
477-
aliasName, this.displayName));
478+
"Neither attribute '%s' nor its alias '%s' was found in attributes for annotation [%s]",
479+
attributeName, aliasName, this.displayName));
480+
}
481+
}
482+
483+
private void assertNotException(String attributeName, Object attributeValue) {
484+
if (attributeValue instanceof Exception) {
485+
throw new IllegalArgumentException(String.format(
486+
"Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]",
487+
attributeName, this.displayName, attributeValue), (Exception) attributeValue);
478488
}
479489
}
480490

481491
private void assertAttributeType(String attributeName, Object attributeValue, Class<?> expectedType) {
482492
if (!expectedType.isInstance(attributeValue)) {
483493
throw new IllegalArgumentException(String.format(
484-
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
485-
attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(),
486-
this.displayName));
494+
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
495+
attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(),
496+
this.displayName));
487497
}
488498
}
489499

@@ -532,6 +542,7 @@ private String valueToString(Object value) {
532542
return String.valueOf(value);
533543
}
534544

545+
535546
/**
536547
* Return an {@link AnnotationAttributes} instance based on the given map.
537548
* <p>If the map is already an {@code AnnotationAttributes} instance, it

spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ else if (value instanceof AnnotationAttributes[]) {
6262
}
6363
}
6464
else if (value instanceof Type) {
65-
value = (classValuesAsString ? ((Type) value).getClassName()
66-
: classLoader.loadClass(((Type) value).getClassName()));
65+
value = (classValuesAsString ? ((Type) value).getClassName() :
66+
classLoader.loadClass(((Type) value).getClassName()));
6767
}
6868
else if (value instanceof Type[]) {
6969
Type[] array = (Type[]) value;
7070
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
7171
for (int i = 0; i < array.length; i++) {
72-
convArray[i] = (classValuesAsString ? array[i].getClassName()
73-
: classLoader.loadClass(array[i].getClassName()));
72+
convArray[i] = (classValuesAsString ? array[i].getClassName() :
73+
classLoader.loadClass(array[i].getClassName()));
7474
}
7575
value = convArray;
7676
}
@@ -90,8 +90,8 @@ else if (value instanceof Class[]) {
9090
result.put(entry.getKey(), value);
9191
}
9292
catch (Exception ex) {
93-
// Class not found - can't resolve class reference in annotation
94-
// attribute.
93+
// Class not found - can't resolve class reference in annotation attribute.
94+
result.put(entry.getKey(), ex);
9595
}
9696
}
9797
return result;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public void typeSafeAttributeAccess() {
5757
attributes.put("number", 42);
5858
attributes.put("anno", nestedAttributes);
5959
attributes.put("annoArray", new AnnotationAttributes[] { nestedAttributes });
60+
attributes.put("unresolvableClass", new ClassNotFoundException("myclass"));
6061

6162
assertThat(attributes.getString("name"), equalTo("dave"));
6263
assertThat(attributes.getStringArray("names"), equalTo(new String[] { "dave", "frank", "hal" }));
@@ -68,6 +69,15 @@ public void typeSafeAttributeAccess() {
6869
assertThat(attributes.<Integer>getNumber("number"), equalTo(42));
6970
assertThat(attributes.getAnnotation("anno").<Integer>getNumber("value"), equalTo(10));
7071
assertThat(attributes.getAnnotationArray("annoArray")[0].getString("name"), equalTo("algernon"));
72+
73+
try {
74+
attributes.getClass("unresolvableClass");
75+
fail("Should have thrown IllegalArgumentException");
76+
}
77+
catch (IllegalArgumentException ex) {
78+
assertTrue(ex.getCause() instanceof ClassNotFoundException);
79+
assertTrue(ex.getMessage().contains("myclass"));
80+
}
7181
}
7282

7383
@Test

0 commit comments

Comments
 (0)