From 6062363738da870048f98c21141dfce6619a0e98 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:32:05 +0200 Subject: [PATCH] Use canonical names in error messages in annotation processing Prior to this commit, we invoked `Class.getName()` when building error messages during annotation processing, resulting in exceptions like the following which use binary names for nested types and arrays. Attribute 'chars' in annotation org.springframework.core.annotation.AnnotationUtilsTests$CharsContainer should be compatible with [C but a [I value was returned This commit switches to canonical names in error messages in annotation processing, resulting in improved such errors messages such as the following. Attribute 'chars' in annotation org.springframework.core.annotation.AnnotationUtilsTests.CharsContainer should be compatible with char[] but a int[] value was returned In addition, this commit introduces a new getCanonicalName(Class) method in ClassUtils, which has effectively been extracted from the following classes where this functionality was previously duplicated. - AttributeMethods - SynthesizedMergedAnnotationInvocationHandler - TypeDescriptor - DefaultRetryPolicy - ReflectiveIndexAccessor Closes gh-36607 --- .../aot/agent/MethodReference.java | 4 +- .../aot/agent/RecordedInvocation.java | 3 +- .../annotation/AbstractMergedAnnotation.java | 3 +- .../core/annotation/AnnotationAttributes.java | 3 +- .../annotation/AnnotationTypeMapping.java | 3 +- .../annotation/AnnotationTypeMappings.java | 4 +- .../core/annotation/AttributeMethods.java | 13 +++--- .../core/annotation/RepeatableContainers.java | 7 +-- ...izedMergedAnnotationInvocationHandler.java | 11 ++--- .../core/annotation/TypeMappedAnnotation.java | 13 +++--- .../core/convert/TypeDescriptor.java | 7 +-- .../core/retry/DefaultRetryPolicy.java | 4 +- .../core/style/SimpleValueStyler.java | 4 +- .../org/springframework/util/ClassUtils.java | 14 ++++++ .../AnnotationTypeMappingsTests.java | 40 ++++++++--------- .../core/annotation/AnnotationUtilsTests.java | 30 ++++++++----- .../ComposedRepeatableAnnotationsTests.java | 14 +++--- ...dAnnotationsRepeatableAnnotationTests.java | 14 +++--- .../annotation/MergedAnnotationsTests.java | 43 ++++++++++++------- .../annotation/RepeatableContainersTests.java | 8 ++-- .../spel/support/ReflectiveIndexAccessor.java | 14 +++--- 21 files changed, 143 insertions(+), 113 deletions(-) diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/MethodReference.java b/spring-core-test/src/main/java/org/springframework/aot/agent/MethodReference.java index 0ac867a1779..966f2297c78 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/MethodReference.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/MethodReference.java @@ -20,6 +20,8 @@ import java.util.Objects; import org.jspecify.annotations.Nullable; +import org.springframework.util.ClassUtils; + /** * Reference to a Java method, identified by its owner class and the method name. * @@ -43,7 +45,7 @@ public final class MethodReference { } public static MethodReference of(Class klass, String methodName) { - return new MethodReference(klass.getCanonicalName(), methodName); + return new MethodReference(ClassUtils.getCanonicalName(klass), methodName); } /** diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java index d56f98d4bff..19c6ad45e59 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/RecordedInvocation.java @@ -25,6 +25,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Record of an invocation of a method relevant to {@link org.springframework.aot.hint.RuntimeHints}. @@ -181,7 +182,7 @@ public final class RecordedInvocation { else { Class instanceType = (getInstance() instanceof Class clazz) ? clazz : getInstance().getClass(); return "<%s> invocation of <%s> on type <%s> with arguments %s".formatted( - getHintType().hintClassName(), getMethodReference(), instanceType.getCanonicalName(), getArguments()); + getHintType().hintClassName(), getMethodReference(), ClassUtils.getCanonicalName(instanceType), getArguments()); } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java index 0c844b10b9a..7f2e090147c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java @@ -24,6 +24,7 @@ import java.util.function.Predicate; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Abstract base class for {@link MergedAnnotation} implementations. @@ -215,7 +216,7 @@ abstract class AbstractMergedAnnotation implements MergedA T value = getAttributeValue(attributeName, type); if (value == null) { throw new NoSuchElementException("No attribute named '" + attributeName + - "' present in merged annotation " + getType().getName()); + "' present in merged annotation " + ClassUtils.getCanonicalName(getType())); } return value; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index a4be0aef1dd..82941489e6d 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -25,6 +25,7 @@ import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -126,7 +127,7 @@ public class AnnotationAttributes extends LinkedHashMap annotationType, boolean validated) { Assert.notNull(annotationType, "'annotationType' must not be null"); this.annotationType = annotationType; - this.displayName = annotationType.getName(); + this.displayName = ClassUtils.getCanonicalName(annotationType); this.validated = validated; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java index 19f123e6ff1..563341654e1 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java @@ -32,6 +32,7 @@ import java.util.Set; import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -649,7 +650,7 @@ final class AnnotationTypeMapping { throw new AnnotationConfigurationException(String.format( "Different @AliasFor mirror values for annotation [%s]%s; attribute '%s' " + "and its alias '%s' are declared with values of [%s] and [%s].", - getAnnotationType().getName(), on, + ClassUtils.getCanonicalName(getAnnotationType()), on, attributes.get(result).getName(), attribute.getName(), ObjectUtils.nullSafeToString(lastValue), diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java index 7c9f595b230..7f1d396badf 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java @@ -28,6 +28,7 @@ import java.util.Set; import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; /** @@ -124,7 +125,8 @@ final class AnnotationTypeMappings { AnnotationUtils.rethrowAnnotationConfigurationException(ex); if (failureLogger.isEnabled()) { failureLogger.log("Failed to introspect " + (meta ? "meta-annotation @" : "annotation @") + - annotationType.getName(), (source != null ? source.getAnnotationType() : null), ex); + ClassUtils.getCanonicalName(annotationType), + (source != null ? ClassUtils.getCanonicalName(source.getAnnotationType()) : null), ex); } } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java index 0e998b84b1a..44007afb4fe 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AttributeMethods.java @@ -26,6 +26,7 @@ import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; @@ -143,8 +144,9 @@ final class AttributeMethods { throw ex; } catch (Throwable ex) { - throw new IllegalStateException("Could not obtain annotation attribute value for " + - get(i).getName() + " declared on @" + getName(annotation.annotationType()), ex); + throw new IllegalStateException( + "Could not obtain annotation attribute value for " + get(i).getName() + + " declared on @" + ClassUtils.getCanonicalName(annotation.annotationType()), ex); } } } @@ -305,13 +307,8 @@ final class AttributeMethods { if (attributeName == null) { return "(none)"; } - String in = (annotationType != null ? " in annotation [" + annotationType.getName() + "]" : ""); + String in = (annotationType != null ? " in annotation [" + ClassUtils.getCanonicalName(annotationType) + "]" : ""); return "attribute '" + attributeName + "'" + in; } - private static String getName(Class clazz) { - String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); - } - } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java index 53d3da336d8..b8462d8f379 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java @@ -26,6 +26,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; @@ -312,7 +313,7 @@ public abstract class RepeatableContainers { if (returnType.componentType() != repeatable) { throw new AnnotationConfigurationException( "Container type [%s] must declare a 'value' attribute for an array of type [%s]" - .formatted(container.getName(), repeatable.getName())); + .formatted(ClassUtils.getCanonicalName(container), ClassUtils.getCanonicalName(repeatable))); } } catch (AnnotationConfigurationException ex) { @@ -321,7 +322,7 @@ public abstract class RepeatableContainers { catch (Throwable ex) { throw new AnnotationConfigurationException( "Invalid declaration of container type [%s] for repeatable annotation [%s]" - .formatted(container.getName(), repeatable.getName()), ex); + .formatted(ClassUtils.getCanonicalName(container), ClassUtils.getCanonicalName(repeatable)), ex); } this.repeatable = repeatable; this.container = container; @@ -331,7 +332,7 @@ public abstract class RepeatableContainers { private Class deduceContainer(Class repeatable) { Repeatable annotation = repeatable.getAnnotation(Repeatable.class); Assert.notNull(annotation, () -> "Annotation type must be a repeatable annotation: " + - "failed to resolve container type for " + repeatable.getName()); + "failed to resolve container type for " + ClassUtils.getCanonicalName(repeatable)); return annotation.value(); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java index 24c21acf6af..d0591716924 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java @@ -136,7 +136,7 @@ final class SynthesizedMergedAnnotationInvocationHandler i private String annotationToString() { String string = this.string; if (string == null) { - StringBuilder builder = new StringBuilder("@").append(getName(this.type)).append('('); + StringBuilder builder = new StringBuilder("@").append(ClassUtils.getCanonicalName(this.type)).append('('); if (this.attributes.size() == 1 && this.attributes.get(0).getName().equals(MergedAnnotation.VALUE)) { // Don't prepend "value=" for an annotation that only declares a "value" attribute. builder.append(toString(getAttributeValue(this.attributes.get(0)))); @@ -208,7 +208,7 @@ final class SynthesizedMergedAnnotationInvocationHandler i return e.name(); } if (type == Class.class) { - return getName((Class) value) + ".class"; + return ClassUtils.getCanonicalName((Class) value) + ".class"; } return String.valueOf(value); } @@ -218,7 +218,7 @@ final class SynthesizedMergedAnnotationInvocationHandler i Class type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType()); return this.annotation.getValue(attributeName, type).orElseThrow( () -> new NoSuchElementException("No value found for attribute named '" + attributeName + - "' in merged annotation " + getName(this.annotation.getType()))); + "' in merged annotation " + ClassUtils.getCanonicalName(this.annotation.getType()))); }); // Clone non-empty arrays so that users cannot alter the contents of values in our cache. @@ -272,9 +272,4 @@ final class SynthesizedMergedAnnotationInvocationHandler i return (A) Proxy.newProxyInstance(classLoader, interfaces, handler); } - private static String getName(Class clazz) { - String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); - } - } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index e7937c815cd..cf784b29743 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -490,7 +490,7 @@ final class TypeMappedAnnotation extends AbstractMergedAnn } if (!type.isInstance(value)) { throw new IllegalArgumentException("Unable to adapt value of type " + - value.getClass().getName() + " to " + type.getName()); + ClassUtils.getCanonicalName(value.getClass()) + " to " + ClassUtils.getCanonicalName(type)); } return (T) value; } @@ -525,8 +525,8 @@ final class TypeMappedAnnotation extends AbstractMergedAnn } if (!attributeType.isInstance(value)) { throw new IllegalStateException("Attribute '" + attribute.getName() + - "' in annotation " + getType().getName() + " should be compatible with " + - attributeType.getName() + " but a " + value.getClass().getName() + + "' in annotation " + ClassUtils.getCanonicalName(getType()) + " should be compatible with " + + ClassUtils.getCanonicalName(attributeType) + " but a " + ClassUtils.getCanonicalName(value.getClass()) + " value was returned"); } return value; @@ -583,7 +583,7 @@ final class TypeMappedAnnotation extends AbstractMergedAnn int attributeIndex = (isFiltered(attributeName) ? -1 : this.mapping.getAttributes().indexOf(attributeName)); if (attributeIndex == -1 && required) { throw new NoSuchElementException("No attribute named '" + attributeName + - "' present in merged annotation " + getType().getName()); + "' present in merged annotation " + ClassUtils.getCanonicalName(getType())); } return attributeIndex; } @@ -660,9 +660,10 @@ final class TypeMappedAnnotation extends AbstractMergedAnn catch (Exception ex) { AnnotationUtils.rethrowAnnotationConfigurationException(ex); if (logger.isEnabled()) { - String type = mapping.getAnnotationType().getName(); + String type = ClassUtils.getCanonicalName(mapping.getAnnotationType()); String item = (mapping.getDistance() == 0 ? "annotation " + type : - "meta-annotation " + type + " from " + mapping.getRoot().getAnnotationType().getName()); + "meta-annotation " + type + " from " + + ClassUtils.getCanonicalName(mapping.getRoot().getAnnotationType())); logger.log("Failed to introspect " + item, source, ex); } return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 3759eff1733..78ba2283b9e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -541,7 +541,7 @@ public class TypeDescriptor implements Serializable { public String toString() { StringBuilder builder = new StringBuilder(); for (Annotation ann : getAnnotations()) { - builder.append('@').append(getName(ann.annotationType())).append(' '); + builder.append('@').append(ClassUtils.getCanonicalName(ann.annotationType())).append(' '); } builder.append(getResolvableType()); return builder.toString(); @@ -726,11 +726,6 @@ public class TypeDescriptor implements Serializable { return new TypeDescriptor(property).nested(nestingLevel); } - private static String getName(Class clazz) { - String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); - } - private interface AnnotatedElementSupplier extends Supplier, Serializable { } diff --git a/spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java b/spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java index 50a1c049aa1..e9faa2b7866 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java +++ b/spring-core/src/main/java/org/springframework/core/retry/DefaultRetryPolicy.java @@ -23,6 +23,7 @@ import java.util.function.Predicate; import org.jspecify.annotations.Nullable; +import org.springframework.util.ClassUtils; import org.springframework.util.ExceptionTypeFilter; import org.springframework.util.backoff.BackOff; @@ -96,8 +97,7 @@ class DefaultRetryPolicy implements RetryPolicy { private static String names(Set> types) { StringJoiner result = new StringJoiner(", ", "[", "]"); for (Class type : types) { - String name = type.getCanonicalName(); - result.add(name != null? name : type.getName()); + result.add(ClassUtils.getCanonicalName(type)); } return result.toString(); } diff --git a/spring-core/src/main/java/org/springframework/core/style/SimpleValueStyler.java b/spring-core/src/main/java/org/springframework/core/style/SimpleValueStyler.java index 3892df69aab..cc208e44da8 100644 --- a/spring-core/src/main/java/org/springframework/core/style/SimpleValueStyler.java +++ b/spring-core/src/main/java/org/springframework/core/style/SimpleValueStyler.java @@ -24,6 +24,8 @@ import java.util.StringJoiner; import java.util.function.Function; import java.util.stream.Collectors; +import org.springframework.util.ClassUtils; + /** * {@link ValueStyler} that converts objects to String form — generally for * debugging purposes — using simple styling conventions that mimic the @@ -45,7 +47,7 @@ public class SimpleValueStyler extends DefaultValueStyler { /** * Default {@link Class} styling function: {@link Class#getCanonicalName()}. */ - public static final Function, String> DEFAULT_CLASS_STYLER = Class::getCanonicalName; + public static final Function, String> DEFAULT_CLASS_STYLER = ClassUtils::getCanonicalName; /** * Default {@link Method} styling function: converts the supplied {@link Method} diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index 50800d0c977..95c613c96d0 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1125,6 +1125,20 @@ public abstract class ClassUtils { return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : ""); } + /** + * Return the {@linkplain Class#getCanonicalName() canonical name} of the given + * class, or the {@linkplain Class#getName() binary name} of the class if the + * canonical name does not exist. + * @param clazz the class + * @return the canonical name of the class, or the binary name as a fallback + * @since 7.1 + */ + public static String getCanonicalName(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + String canonicalName = clazz.getCanonicalName(); + return (canonicalName != null ? canonicalName : clazz.getName()); + } + /** * Return the qualified name of the given class: usually simply * the class name, but component type class name + "[]" for arrays. diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java index 0eac833cc5e..78a1e3f62fd 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationTypeMappingsTests.java @@ -102,7 +102,7 @@ class AnnotationTypeMappingsTests { .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForWithBothValueAndAttribute.class)) .withMessage("In @AliasFor declared on attribute 'test' in annotation [%s], attribute 'attribute' " + "and its alias 'value' are present with values of 'foo' and 'bar', but only one is permitted.", - AliasForWithBothValueAndAttribute.class.getName()); + AliasForWithBothValueAndAttribute.class.getCanonicalName()); } @Test @@ -111,7 +111,7 @@ class AnnotationTypeMappingsTests { .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelfNonExistingAttribute.class)) .withMessage("@AliasFor declaration on attribute 'test' in annotation [%s] " + "declares an alias for 'missing' which is not present.", - AliasForToSelfNonExistingAttribute.class.getName()); + AliasForToSelfNonExistingAttribute.class.getCanonicalName()); } @Test @@ -119,8 +119,8 @@ class AnnotationTypeMappingsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToOtherNonExistingAttribute.class)) .withMessage("Attribute 'test' in annotation [%s] is declared as an @AliasFor nonexistent " + - "attribute 'missing' in annotation [%s].", AliasForToOtherNonExistingAttribute.class.getName(), - AliasForToOtherNonExistingAttributeTarget.class.getName()); + "attribute 'missing' in annotation [%s].", AliasForToOtherNonExistingAttribute.class.getCanonicalName(), + AliasForToOtherNonExistingAttributeTarget.class.getCanonicalName()); } @Test @@ -129,7 +129,7 @@ class AnnotationTypeMappingsTests { .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelf.class)) .withMessage("@AliasFor declaration on attribute 'test' in annotation [%s] points to itself. " + "Specify 'annotation' to point to a same-named attribute on a meta-annotation.", - AliasForToSelf.class.getName()); + AliasForToSelf.class.getCanonicalName()); } @Test @@ -147,13 +147,13 @@ class AnnotationTypeMappingsTests { .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForWithIncompatibleReturnTypes.class)) .withMessage("Misconfigured aliases: attribute 'test' in annotation [%s] and attribute 'test' " + "in annotation [%s] must declare the same return type.", - AliasForWithIncompatibleReturnTypes.class.getName(), - AliasForWithIncompatibleReturnTypesTarget.class.getName()); + AliasForWithIncompatibleReturnTypes.class.getCanonicalName(), + AliasForWithIncompatibleReturnTypesTarget.class.getCanonicalName()); } @Test void forAnnotationTypeWhenAliasForToSelfAnnotatedToOtherAttribute() { - String annotationType = AliasForToSelfAnnotatedToOtherAttribute.class.getName(); + String annotationType = AliasForToSelfAnnotatedToOtherAttribute.class.getCanonicalName(); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForToSelfAnnotatedToOtherAttribute.class)) .withMessage("Attribute 'b' in annotation [%1$s] must be declared as an @AliasFor attribute 'a' in " + @@ -167,8 +167,8 @@ class AnnotationTypeMappingsTests { } private void assertMixedImplicitAndExplicitAliases(Class annotationType, String overriddenAttribute) { - String annotationName = annotationType.getName(); - String metaAnnotationName = AliasPair.class.getName(); + String annotationName = annotationType.getCanonicalName(); + String metaAnnotationName = AliasPair.class.getCanonicalName(); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(annotationType)) .withMessage("Attribute 'b' in annotation [" + annotationName + @@ -180,8 +180,8 @@ class AnnotationTypeMappingsTests { void forAnnotationTypeWhenAliasForNonMetaAnnotated() { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForNonMetaAnnotated.class)) - .withMessage("@AliasFor declaration on attribute 'test' in annotation [" + AliasForNonMetaAnnotated.class.getName() + - "] declares an alias for attribute 'test' in annotation [" + AliasForNonMetaAnnotatedTarget.class.getName() + + .withMessage("@AliasFor declaration on attribute 'test' in annotation [" + AliasForNonMetaAnnotated.class.getCanonicalName() + + "] declares an alias for attribute 'test' in annotation [" + AliasForNonMetaAnnotatedTarget.class.getCanonicalName() + "] which is not meta-present."); } @@ -189,8 +189,8 @@ class AnnotationTypeMappingsTests { void forAnnotationTypeWhenAliasForSelfWithDifferentDefaults() { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForSelfWithDifferentDefaults.class)) - .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithDifferentDefaults.class.getName() + - "] and attribute 'b' in annotation [" + AliasForSelfWithDifferentDefaults.class.getName() + + .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithDifferentDefaults.class.getCanonicalName() + + "] and attribute 'b' in annotation [" + AliasForSelfWithDifferentDefaults.class.getCanonicalName() + "] must declare the same default value."); } @@ -198,8 +198,8 @@ class AnnotationTypeMappingsTests { void forAnnotationTypeWhenAliasForSelfWithMissingDefault() { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasForSelfWithMissingDefault.class)) - .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithMissingDefault.class.getName() + - "] and attribute 'b' in annotation [" + AliasForSelfWithMissingDefault.class.getName() + + .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasForSelfWithMissingDefault.class.getCanonicalName() + + "] and attribute 'b' in annotation [" + AliasForSelfWithMissingDefault.class.getCanonicalName() + "] must declare default values."); } @@ -207,8 +207,8 @@ class AnnotationTypeMappingsTests { void forAnnotationTypeWhenAliasWithExplicitMirrorAndDifferentDefaults() { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> AnnotationTypeMappings.forAnnotationType(AliasWithExplicitMirrorAndDifferentDefaults.class)) - .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getName() + - "] and attribute 'c' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getName() + + .withMessage("Misconfigured aliases: attribute 'a' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getCanonicalName() + + "] and attribute 'c' in annotation [" + AliasWithExplicitMirrorAndDifferentDefaults.class.getCanonicalName() + "] must declare the same default value."); } @@ -352,8 +352,8 @@ class AnnotationTypeMappingsTests { AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(AliasPair.class).get(0); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> resolveMirrorSets(mapping, WithDifferentValueAliasPair.class, AliasPair.class)) - .withMessage("Different @AliasFor mirror values for annotation [" + AliasPair.class.getName() + "] declared on " + - WithDifferentValueAliasPair.class.getName() + + .withMessage("Different @AliasFor mirror values for annotation [" + AliasPair.class.getCanonicalName() + + "] declared on " + WithDifferentValueAliasPair.class.getName() + "; attribute 'a' and its alias 'b' are declared with values of [test1] and [test2]."); } 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 d66ee5fba82..6ee9fbf63ac 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 @@ -718,11 +718,11 @@ class AnnotationUtilsTests { ImplicitAliasesWithMissingDefaultValuesContextConfig config = clazz.getAnnotation(annotationType); assertThat(config).isNotNull(); - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - synthesizeAnnotation(config, clazz)) + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> synthesizeAnnotation(config, clazz)) .withMessageStartingWith("Misconfigured aliases:") - .withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]") - .withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]") + .withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName()) + .withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName()) .withMessageContaining("default values"); } @@ -733,11 +733,11 @@ class AnnotationUtilsTests { ImplicitAliasesWithDifferentDefaultValuesContextConfig.class; ImplicitAliasesWithDifferentDefaultValuesContextConfig config = clazz.getAnnotation(annotationType); assertThat(config).isNotNull(); - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - synthesizeAnnotation(config, clazz)) + assertThatExceptionOfType(AnnotationConfigurationException.class) + .isThrownBy(() -> synthesizeAnnotation(config, clazz)) .withMessageStartingWith("Misconfigured aliases:") - .withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]") - .withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]") + .withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName()) + .withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName()) .withMessageContaining("same default value"); } @@ -919,8 +919,18 @@ class AnnotationUtilsTests { Map map = Collections.singletonMap(VALUE, 42L); assertThatIllegalStateException().isThrownBy(() -> synthesizeAnnotation(map, Component.class, null).value()) - .withMessageContaining("Attribute 'value' in annotation org.springframework.core.testfixture.stereotype.Component " + - "should be compatible with java.lang.String but a java.lang.Long value was returned"); + .withMessageContaining("Attribute 'value' in annotation %s should be compatible with " + + "java.lang.String but a java.lang.Long value was returned", + Component.class.getCanonicalName()); + } + + @Test + void synthesizeAnnotationFromMapWithAttributeOfIncorrectArrayType() { + Map map = Collections.singletonMap(VALUE, new int[] {42}); + assertThatIllegalStateException().isThrownBy(() -> + synthesizeAnnotation(map, CharsContainer.class, null).chars()) + .withMessageContaining("Attribute 'chars' in annotation %s should be compatible with " + + "char[] but a int[] value was returned", CharsContainer.class.getCanonicalName()); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java index 1b4d561f88c..5bcba937773 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java @@ -183,32 +183,32 @@ class ComposedRepeatableAnnotationsTests { assertThatIllegalArgumentException().isThrownBy(throwingCallable) .withMessageStartingWith("Annotation type must be a repeatable annotation") .withMessageContaining("failed to resolve container type for") - .withMessageContaining(NonRepeatable.class.getName()); + .withMessageContaining(NonRepeatable.class.getCanonicalName()); } private void expectContainerMissingValueAttribute(ThrowingCallable throwingCallable) { assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable) .withMessageStartingWith("Invalid declaration of container type") - .withMessageContaining(ContainerMissingValueAttribute.class.getName()) + .withMessageContaining(ContainerMissingValueAttribute.class.getCanonicalName()) .withMessageContaining("for repeatable annotation") - .withMessageContaining(InvalidRepeatable.class.getName()) + .withMessageContaining(InvalidRepeatable.class.getCanonicalName()) .withCauseExactlyInstanceOf(NoSuchMethodException.class); } private void expectContainerWithNonArrayValueAttribute(ThrowingCallable throwingCallable) { assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable) .withMessageStartingWith("Container type") - .withMessageContaining(ContainerWithNonArrayValueAttribute.class.getName()) + .withMessageContaining(ContainerWithNonArrayValueAttribute.class.getCanonicalName()) .withMessageContaining("must declare a 'value' attribute for an array of type") - .withMessageContaining(InvalidRepeatable.class.getName()); + .withMessageContaining(InvalidRepeatable.class.getCanonicalName()); } private void expectContainerWithArrayValueAttributeButWrongComponentType(ThrowingCallable throwingCallable) { assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(throwingCallable) .withMessageStartingWith("Container type") - .withMessageContaining(ContainerWithArrayValueAttributeButWrongComponentType.class.getName()) + .withMessageContaining(ContainerWithArrayValueAttributeButWrongComponentType.class.getCanonicalName()) .withMessageContaining("must declare a 'value' attribute for an array of type") - .withMessageContaining(InvalidRepeatable.class.getName()); + .withMessageContaining(InvalidRepeatable.class.getCanonicalName()); } private void assertGetRepeatableAnnotations(AnnotatedElement element) { diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java index 7c9e3032bc6..a0e74354189 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java @@ -282,16 +282,16 @@ class MergedAnnotationsRepeatableAnnotationTests { private void nonRepeatableRequirements(Exception ex) { assertThat(ex) .hasMessageStartingWith("Annotation type must be a repeatable annotation") - .hasMessageContaining("failed to resolve container type for", NonRepeatable.class.getName()); + .hasMessageContaining("failed to resolve container type for", NonRepeatable.class.getCanonicalName()); } private void missingValueAttributeRequirements(Exception ex) { assertThat(ex) .hasMessageStartingWith("Invalid declaration of container type") .hasMessageContaining( - ContainerMissingValueAttribute.class.getName(), + ContainerMissingValueAttribute.class.getCanonicalName(), "for repeatable annotation", - InvalidRepeatable.class.getName()) + InvalidRepeatable.class.getCanonicalName()) .hasCauseInstanceOf(NoSuchMethodException.class); } @@ -299,18 +299,18 @@ class MergedAnnotationsRepeatableAnnotationTests { assertThat(ex) .hasMessageStartingWith("Container type") .hasMessageContaining( - ContainerWithNonArrayValueAttribute.class.getName(), + ContainerWithNonArrayValueAttribute.class.getCanonicalName(), "must declare a 'value' attribute for an array of type", - InvalidRepeatable.class.getName()); + InvalidRepeatable.class.getCanonicalName()); } private void wrongComponentTypeRequirements(Exception ex) { assertThat(ex) .hasMessageStartingWith("Container type") .hasMessageContaining( - ContainerWithArrayValueAttributeButWrongComponentType.class.getName(), + ContainerWithArrayValueAttributeButWrongComponentType.class.getCanonicalName(), "must declare a 'value' attribute for an array of type", - InvalidRepeatable.class.getName()); + InvalidRepeatable.class.getCanonicalName()); } private static ThrowableTypeAssert assertThatAnnotationConfigurationException() { diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 276fb7167c7..1b7ad6dddfd 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -1605,7 +1605,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("@AliasFor declaration on attribute 'foo' in annotation") - .withMessageContaining(AliasForWithMissingAttributeDeclaration.class.getName()) + .withMessageContaining(AliasForWithMissingAttributeDeclaration.class.getCanonicalName()) .withMessageContaining("points to itself"); } @@ -1618,7 +1618,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("In @AliasFor declared on attribute 'foo' in annotation") - .withMessageContaining(AliasForWithDuplicateAttributeDeclaration.class.getName()) + .withMessageContaining(AliasForWithDuplicateAttributeDeclaration.class.getCanonicalName()) .withMessageContaining("attribute 'attribute' and its alias 'value' are present with values of 'baz' and 'bar'"); } @@ -1630,7 +1630,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("@AliasFor declaration on attribute 'foo' in annotation") - .withMessageContaining(AliasForNonexistentAttribute.class.getName()) + .withMessageContaining(AliasForNonexistentAttribute.class.getCanonicalName()) .withMessageContaining("declares an alias for 'bar' which is not present"); } @@ -1643,7 +1643,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessage("@AliasFor declaration on attribute 'bar' in annotation [" + - AliasForWithMirroredAliasForWrongAttribute.class.getName() + + AliasForWithMirroredAliasForWrongAttribute.class.getCanonicalName() + "] declares an alias for 'quux' which is not present."); } @@ -1655,7 +1655,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("Misconfigured aliases") - .withMessageContaining(AliasForAttributeOfDifferentType.class.getName()) + .withMessageContaining(AliasForAttributeOfDifferentType.class.getCanonicalName()) .withMessageContaining("attribute 'foo'") .withMessageContaining("attribute 'bar'") .withMessageContaining("same return type"); @@ -1669,7 +1669,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("Misconfigured aliases") - .withMessageContaining(AliasForWithMissingDefaultValues.class.getName()) + .withMessageContaining(AliasForWithMissingDefaultValues.class.getCanonicalName()) .withMessageContaining("attribute 'foo' in annotation") .withMessageContaining("attribute 'bar' in annotation") .withMessageContaining("default values"); @@ -1684,7 +1684,7 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("Misconfigured aliases") - .withMessageContaining(AliasForAttributeWithDifferentDefaultValue.class.getName()) + .withMessageContaining(AliasForAttributeWithDifferentDefaultValue.class.getCanonicalName()) .withMessageContaining("attribute 'foo' in annotation") .withMessageContaining("attribute 'bar' in annotation") .withMessageContaining("same default value"); @@ -1699,9 +1699,9 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(annotation)) .withMessageStartingWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation") - .withMessageContaining(AliasedComposedTestConfigurationNotMetaPresent.class.getName()) + .withMessageContaining(AliasedComposedTestConfigurationNotMetaPresent.class.getCanonicalName()) .withMessageContaining("declares an alias for attribute 'location' in annotation") - .withMessageContaining(TestConfiguration.class.getName()) + .withMessageContaining(TestConfiguration.class.getCanonicalName()) .withMessageContaining("not meta-present"); } @@ -1792,8 +1792,8 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(clazz, config)) .withMessageStartingWith("Misconfigured aliases:") - .withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]") - .withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]") + .withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName()) + .withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName()) .withMessageContaining("default values"); } @@ -1807,8 +1807,8 @@ class MergedAnnotationsTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> MergedAnnotation.from(clazz, config)) .withMessageStartingWith("Misconfigured aliases:") - .withMessageContaining("attribute 'location1' in annotation [" + annotationType.getName() + "]") - .withMessageContaining("attribute 'location2' in annotation [" + annotationType.getName() + "]") + .withMessageContaining("attribute 'location1' in annotation [%s]", annotationType.getCanonicalName()) + .withMessageContaining("attribute 'location2' in annotation [%s]", annotationType.getCanonicalName()) .withMessageContaining("same default value"); } @@ -1978,9 +1978,20 @@ class MergedAnnotationsTests { MergedAnnotation annotation = MergedAnnotation.of(Component.class, map); assertThatIllegalStateException() .isThrownBy(() -> annotation.synthesize().value()) - .withMessage("Attribute 'value' in annotation " + - "org.springframework.core.testfixture.stereotype.Component should be " + - "compatible with java.lang.String but a java.lang.Long value was returned"); + .withMessage("Attribute 'value' in annotation %s should be compatible with " + + "java.lang.String but a java.lang.Long value was returned", + Component.class.getCanonicalName()); + } + + @Test + void synthesizeFromMapWithAttributeOfIncorrectArrayType() { + Map map = Collections.singletonMap("value", new int[] {42}); + MergedAnnotation annotation = MergedAnnotation.of(TestPropertySource.class, map); + assertThatIllegalStateException() + .isThrownBy(() -> annotation.synthesize().value()) + .withMessage("Attribute 'value' in annotation %s should be compatible with " + + "java.lang.String[] but a int[] value was returned", + TestPropertySource.class.getCanonicalName()); } @Test diff --git a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java index 063abc8a0ee..d4c886e4de2 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java @@ -120,7 +120,7 @@ class RepeatableContainersTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> RepeatableContainers.explicitRepeatable(ExplicitRepeatable.class, InvalidNoValue.class)) .withMessageContaining("Invalid declaration of container type [%s] for repeatable annotation [%s]", - InvalidNoValue.class.getName(), ExplicitRepeatable.class.getName()); + InvalidNoValue.class.getCanonicalName(), ExplicitRepeatable.class.getCanonicalName()); } @Test @@ -128,7 +128,7 @@ class RepeatableContainersTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> RepeatableContainers.explicitRepeatable(ExplicitRepeatable.class, InvalidNotArray.class)) .withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]", - InvalidNotArray.class.getName(), ExplicitRepeatable.class.getName()); + InvalidNotArray.class.getCanonicalName(), ExplicitRepeatable.class.getCanonicalName()); } @Test @@ -136,7 +136,7 @@ class RepeatableContainersTests { assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> RepeatableContainers.explicitRepeatable(ExplicitRepeatable.class, InvalidWrongArrayType.class)) .withMessage("Container type [%s] must declare a 'value' attribute for an array of type [%s]", - InvalidWrongArrayType.class.getName(), ExplicitRepeatable.class.getName()); + InvalidWrongArrayType.class.getCanonicalName(), ExplicitRepeatable.class.getCanonicalName()); } @Test @@ -151,7 +151,7 @@ class RepeatableContainersTests { assertThatIllegalArgumentException() .isThrownBy(() -> RepeatableContainers.explicitRepeatable(ExplicitRepeatable.class, null)) .withMessage("Annotation type must be a repeatable annotation: failed to resolve container type for %s", - ExplicitRepeatable.class.getName()); + ExplicitRepeatable.class.getCanonicalName()); } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveIndexAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveIndexAccessor.java index f5f181b7ada..3dcd42869c5 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveIndexAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveIndexAccessor.java @@ -156,7 +156,8 @@ public class ReflectiveIndexAccessor implements CompilableIndexAccessor { } catch (Exception ex) { throw new IllegalArgumentException("Failed to find public read-method '%s(%s)' in class '%s'." - .formatted(readMethodName, getName(indexType), getName(targetType))); + .formatted(readMethodName, ClassUtils.getCanonicalName(indexType), + ClassUtils.getCanonicalName(targetType))); } this.readMethodToInvoke = ClassUtils.getPubliclyAccessibleMethodIfPossible(this.readMethod, targetType); @@ -170,8 +171,9 @@ public class ReflectiveIndexAccessor implements CompilableIndexAccessor { } catch (Exception ex) { throw new IllegalArgumentException("Failed to find public write-method '%s(%s, %s)' in class '%s'." - .formatted(writeMethodName, getName(indexType), getName(indexedValueType), - getName(targetType))); + .formatted(writeMethodName, ClassUtils.getCanonicalName(indexType), + ClassUtils.getCanonicalName(indexedValueType), + ClassUtils.getCanonicalName(targetType))); } this.writeMethodToInvoke = ClassUtils.getPubliclyAccessibleMethodIfPossible(writeMethod, targetType); ReflectionUtils.makeAccessible(this.writeMethodToInvoke); @@ -271,10 +273,4 @@ public class ReflectiveIndexAccessor implements CompilableIndexAccessor { mv.visitMethodInsn(opcode, classDesc, methodName, methodDescr, isInterface); } - - private static String getName(Class clazz) { - String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); - } - }