mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Align with JDK by throwing TypeNotPresentException in MergedAnnotations
Prior to this commit, we used ClassUtils.resolveClassName() in TypeMappedAnnotation.adapt(...) which throws an IllegalStateException or IllegalArgumentException if a type referenced by an annotation attribute cannot be loaded. However, if such an error occurs while using the JDK's reflection APIs, a TypeNotPresentException is thrown instead. In order to align with the standard behavior of the JDK, this commit modifies TypeMappedAnnotation.adapt(...) to use ClassUtils.forName() and throw a TypeNotPresentException in such scenarios. This commit also makes similar changes in MergedAnnotationReadingVisitor and ClassFileAnnotationDelegate. Closes gh-36593
This commit is contained in:
+13
-2
@@ -450,7 +450,12 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
value = clazz.getName();
|
||||
}
|
||||
else if (value instanceof String str && type == Class.class) {
|
||||
value = ClassUtils.resolveClassName(str, getClassLoader());
|
||||
try {
|
||||
value = ClassUtils.forName(str, getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
throw new TypeNotPresentException(str, ex);
|
||||
}
|
||||
}
|
||||
else if (value instanceof Class<?>[] classes && type == String[].class) {
|
||||
String[] names = new String[classes.length];
|
||||
@@ -461,8 +466,14 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
}
|
||||
else if (value instanceof String[] names && type == Class[].class) {
|
||||
Class<?>[] classes = new Class<?>[names.length];
|
||||
ClassLoader classLoader = getClassLoader();
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader());
|
||||
try {
|
||||
classes[i] = ClassUtils.forName(names[i], classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
throw new TypeNotPresentException(names[i], ex);
|
||||
}
|
||||
}
|
||||
value = classes;
|
||||
}
|
||||
|
||||
+14
-2
@@ -101,7 +101,13 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Enum<E>> void visitEnum(String descriptor, String value, Consumer<E> consumer) {
|
||||
String className = Type.getType(descriptor).getClassName();
|
||||
Class<E> type = (Class<E>) ClassUtils.resolveClassName(className, this.classLoader);
|
||||
Class<E> type = null;
|
||||
try {
|
||||
type = (Class<E>) ClassUtils.forName(className, this.classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
throw new TypeNotPresentException(className, ex);
|
||||
}
|
||||
consumer.accept(Enum.valueOf(type, value));
|
||||
}
|
||||
|
||||
@@ -113,7 +119,13 @@ class MergedAnnotationReadingVisitor<A extends Annotation> extends AnnotationVis
|
||||
if (AnnotationFilter.PLAIN.matches(className)) {
|
||||
return null;
|
||||
}
|
||||
Class<T> type = (Class<T>) ClassUtils.resolveClassName(className, this.classLoader);
|
||||
Class<T> type = null;
|
||||
try {
|
||||
type = (Class<T>) ClassUtils.forName(className, this.classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
throw new TypeNotPresentException(className, ex);
|
||||
}
|
||||
return new MergedAnnotationReadingVisitor<>(this.classLoader, this.source, type, consumer);
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -139,7 +139,12 @@ abstract class ClassFileAnnotationDelegate {
|
||||
|
||||
private static Class<?> loadEnumClass(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) {
|
||||
String className = ClassFileAnnotationMetadata.resolveTypeName(enumValue.classSymbol());
|
||||
return ClassUtils.resolveClassName(className, classLoader);
|
||||
try {
|
||||
return ClassUtils.forName(className, classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException | LinkageError ex) {
|
||||
throw new TypeNotPresentException(className, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||
|
||||
+18
-17
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.throwable;
|
||||
|
||||
@@ -143,16 +144,16 @@ class TypeMappedAnnotationTests {
|
||||
Map.of(attributeName, "com.example.DoesNotExist"));
|
||||
|
||||
// 0) Sanity check MergedAnnotation.getClass() behavior.
|
||||
assertThatIllegalArgumentException()
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClass(attributeName))
|
||||
.withMessage("Could not find class [com.example.DoesNotExist]")
|
||||
.withCauseInstanceOf(ClassNotFoundException.class);
|
||||
.withMessageContaining("com.example.DoesNotExist")
|
||||
.withCauseExactlyInstanceOf(ClassNotFoundException.class);
|
||||
|
||||
var map = mergedAnnotation.asMap(Adapt.values(false, true));
|
||||
|
||||
// 1) Attribute should be present, even though its value is an exception.
|
||||
assertThat(map).containsKey(attributeName);
|
||||
assertThat(map.get(attributeName)).asInstanceOf(throwable(IllegalArgumentException.class))
|
||||
assertThat(map.get(attributeName)).asInstanceOf(throwable(TypeNotPresentException.class))
|
||||
.hasMessageContaining("com.example.DoesNotExist");
|
||||
}
|
||||
|
||||
@@ -163,16 +164,16 @@ class TypeMappedAnnotationTests {
|
||||
Map.of(attributeName, new String[] { "com.example.DoesNotExist" }));
|
||||
|
||||
// 0) Sanity check MergedAnnotation.getClassArray() behavior.
|
||||
assertThatIllegalArgumentException()
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClassArray(attributeName))
|
||||
.withMessage("Could not find class [com.example.DoesNotExist]")
|
||||
.withMessageContaining("com.example.DoesNotExist")
|
||||
.withCauseExactlyInstanceOf(ClassNotFoundException.class);
|
||||
|
||||
var map = mergedAnnotation.asMap(Adapt.values(false, true));
|
||||
|
||||
// 1) Attribute should be present, even though its value is an exception.
|
||||
assertThat(map).containsKey(attributeName);
|
||||
assertThat(map.get(attributeName)).asInstanceOf(throwable(IllegalArgumentException.class))
|
||||
assertThat(map.get(attributeName)).asInstanceOf(throwable(TypeNotPresentException.class))
|
||||
.hasMessageContaining("com.example.DoesNotExist");
|
||||
}
|
||||
}
|
||||
@@ -188,16 +189,16 @@ class TypeMappedAnnotationTests {
|
||||
Map.of(attributeName, "com.example.DoesNotExist"));
|
||||
|
||||
// 0) Sanity check MergedAnnotation.getClass() behavior.
|
||||
assertThatIllegalArgumentException()
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClass(attributeName))
|
||||
.withMessage("Could not find class [com.example.DoesNotExist]")
|
||||
.withCauseInstanceOf(ClassNotFoundException.class);
|
||||
.withMessageContaining("com.example.DoesNotExist")
|
||||
.withCauseExactlyInstanceOf(ClassNotFoundException.class);
|
||||
|
||||
var attributes = mergedAnnotation.asAnnotationAttributes(Adapt.values(false, true));
|
||||
|
||||
// 1) Attribute should be present, even though its value is an exception.
|
||||
assertThat(attributes).containsKey(attributeName);
|
||||
assertThat(attributes.get(attributeName)).asInstanceOf(throwable(IllegalArgumentException.class))
|
||||
assertThat(attributes.get(attributeName)).asInstanceOf(throwable(TypeNotPresentException.class))
|
||||
.hasMessageContaining("com.example.DoesNotExist");
|
||||
|
||||
// 2) Accessing the attribute via AnnotationAttributes.getClassArray() should throw an
|
||||
@@ -213,16 +214,16 @@ class TypeMappedAnnotationTests {
|
||||
Map.of(attributeName, new String[] { "com.example.DoesNotExist" }));
|
||||
|
||||
// 0) Sanity check MergedAnnotation.getClassArray() behavior.
|
||||
assertThatIllegalArgumentException()
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClassArray(attributeName))
|
||||
.withMessage("Could not find class [com.example.DoesNotExist]")
|
||||
.withCauseInstanceOf(ClassNotFoundException.class);
|
||||
.withMessageContaining("com.example.DoesNotExist")
|
||||
.withCauseExactlyInstanceOf(ClassNotFoundException.class);
|
||||
|
||||
var attributes = mergedAnnotation.asAnnotationAttributes(Adapt.values(false, true));
|
||||
|
||||
// 1) Attribute should be present, even though its value is an exception.
|
||||
assertThat(attributes).containsKey(attributeName);
|
||||
assertThat(attributes.get(attributeName)).asInstanceOf(throwable(IllegalArgumentException.class))
|
||||
assertThat(attributes.get(attributeName)).asInstanceOf(throwable(TypeNotPresentException.class))
|
||||
.hasMessageContaining("com.example.DoesNotExist");
|
||||
|
||||
// 2) Accessing the attribute via AnnotationAttributes.getClassArray() should throw an
|
||||
@@ -236,9 +237,9 @@ class TypeMappedAnnotationTests {
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessageMatching("""
|
||||
Attribute '%s' for annotation \\[.+?\\] was not resolvable \
|
||||
due to exception \\[.+?IllegalArgumentException.+?\\]""".formatted(attributeName))
|
||||
due to exception \\[.+?TypeNotPresentException.+?\\]""".formatted(attributeName))
|
||||
.havingCause()
|
||||
.isExactlyInstanceOf(IllegalArgumentException.class)
|
||||
.isExactlyInstanceOf(TypeNotPresentException.class)
|
||||
.withMessageContaining("com.example.DoesNotExist")
|
||||
.havingCause()
|
||||
.isExactlyInstanceOf(ClassNotFoundException.class)
|
||||
|
||||
+4
-2
@@ -27,7 +27,7 @@ import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link SimpleAnnotationMetadata} and
|
||||
@@ -59,7 +59,9 @@ class DefaultAnnotationMetadataTests extends AbstractAnnotationMetadataTests {
|
||||
.getAnnotations()
|
||||
.get(ClassAttributes.class);
|
||||
assertThat(mergedAnnotation.getStringArray("types")).contains("javax.annotation.meta.When");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> mergedAnnotation.getClassArray("types"));
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClassArray("types"))
|
||||
.withMessageContaining("javax.annotation.meta.When");
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -27,7 +27,7 @@ import org.springframework.core.type.AbstractAnnotationMetadataTests;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link SimpleAnnotationMetadata} and
|
||||
@@ -58,7 +58,9 @@ class SimpleAnnotationMetadataTests extends AbstractAnnotationMetadataTests {
|
||||
.getAnnotations()
|
||||
.get(ClassAttributes.class);
|
||||
assertThat(mergedAnnotation.getStringArray("types")).contains("javax.annotation.meta.When");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> mergedAnnotation.getClassArray("types"));
|
||||
assertThatExceptionOfType(TypeNotPresentException.class)
|
||||
.isThrownBy(() -> mergedAnnotation.getClassArray("types"))
|
||||
.withMessageContaining("javax.annotation.meta.When");
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user