mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Fix annotation arrays support in ClassFile metadata
As of gh-33616, Spring now supports metadata reading with the ClassFile API on JDK 24+ runtimes. This commit fixes a bug where `ArrayStoreException` were thrown when reading annotation attribute values for arrays. Fixes gh-35252
This commit is contained in:
+36
-3
@@ -25,6 +25,7 @@ import java.lang.constant.ClassDesc;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -86,7 +87,7 @@ abstract class ClassFileAnnotationMetadata {
|
||||
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
|
||||
}
|
||||
case AnnotationValue.OfClass classValue -> {
|
||||
return fromTypeDescriptor(classValue.className().stringValue());
|
||||
return loadClass(classValue.className().stringValue(), classLoader);
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return parseEnum(enumValue, classLoader);
|
||||
@@ -103,6 +104,16 @@ abstract class ClassFileAnnotationMetadata {
|
||||
classDesc.packageName() + "." + classDesc.displayName();
|
||||
}
|
||||
|
||||
private static Class<?> loadClass(String className, @Nullable ClassLoader classLoader) {
|
||||
try {
|
||||
String name = fromTypeDescriptor(className);
|
||||
return ClassUtils.forName(name, classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
||||
if (arrayValue.values().isEmpty()) {
|
||||
return new Object[0];
|
||||
@@ -119,10 +130,10 @@ abstract class ClassFileAnnotationMetadata {
|
||||
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
||||
}
|
||||
default -> {
|
||||
Object firstResolvedValue = readAnnotationValue(className, arrayValue.values().getFirst(), classLoader);
|
||||
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
||||
return stream
|
||||
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
||||
.toArray(s -> (Object[]) Array.newInstance(firstResolvedValue.getClass(), s));
|
||||
.toArray(s -> (Object[]) Array.newInstance(arrayElementType, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +150,28 @@ abstract class ClassFileAnnotationMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||
AnnotationValue firstValue = values.getFirst();
|
||||
switch (firstValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue().getClass();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation _ -> {
|
||||
return MergedAnnotation.class;
|
||||
}
|
||||
case AnnotationValue.OfClass _ -> {
|
||||
return Class.class;
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return loadClass(enumValue.className().stringValue(), classLoader);
|
||||
}
|
||||
default -> {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record Source(Annotation entryName) {
|
||||
|
||||
}
|
||||
|
||||
+19
-11
@@ -290,11 +290,11 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
void getComplexAttributeTypesReturnsAll() {
|
||||
MultiValueMap<String, Object> attributes =
|
||||
get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "type", "subAnnotation");
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
|
||||
assertThat(attributes.get("names")).hasSize(1);
|
||||
assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"});
|
||||
assertThat(attributes.get("count")).containsExactlyInAnyOrder(TestEnum.ONE);
|
||||
assertThat(attributes.get("type")).containsExactlyInAnyOrder(TestEnum.class);
|
||||
assertThat(attributes.get("count").get(0)).isEqualTo(new TestEnum[]{TestEnum.ONE, TestEnum.TWO});
|
||||
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{TestEnum.class});
|
||||
assertThat(attributes.get("subAnnotation")).hasSize(1);
|
||||
}
|
||||
|
||||
@@ -312,8 +312,8 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
void getAnnotationAttributeIntType() {
|
||||
MultiValueMap<String, Object> attributes =
|
||||
get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "type", "subAnnotation");
|
||||
assertThat(attributes.get("type")).contains(int.class);
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
|
||||
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{int.class});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -454,13 +454,13 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
}
|
||||
|
||||
|
||||
@ComplexAttributes(names = {"first", "second"}, count = TestEnum.ONE,
|
||||
type = TestEnum.class, subAnnotation = @SubAnnotation(name="spring"))
|
||||
@ComplexAttributes(names = {"first", "second"}, count = {TestEnum.ONE, TestEnum.TWO},
|
||||
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"))
|
||||
@Metadata(mv = {42})
|
||||
public static class WithComplexAttributeTypes {
|
||||
}
|
||||
|
||||
@ComplexAttributes(names = "void", count = TestEnum.ONE, type = int.class,
|
||||
@ComplexAttributes(names = "void", count = TestEnum.ONE, types = int.class,
|
||||
subAnnotation = @SubAnnotation(name="spring"))
|
||||
public static class WithIntType {
|
||||
|
||||
@@ -471,9 +471,9 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
|
||||
String[] names();
|
||||
|
||||
TestEnum count();
|
||||
TestEnum[] count();
|
||||
|
||||
Class<?> type();
|
||||
Class<?>[] types();
|
||||
|
||||
SubAnnotation subAnnotation();
|
||||
}
|
||||
@@ -484,7 +484,15 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
}
|
||||
|
||||
public enum TestEnum {
|
||||
ONE, TWO, THREE
|
||||
ONE {
|
||||
|
||||
},
|
||||
TWO {
|
||||
|
||||
},
|
||||
THREE {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@RepeatableAnnotation(name = "first")
|
||||
|
||||
Reference in New Issue
Block a user