Skip to content

Commit 2b7f88e

Browse files
committed
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
1 parent d8804c7 commit 2b7f88e

File tree

2 files changed

+55
-14
lines changed

2 files changed

+55
-14
lines changed

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

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.reflect.Array;
2626
import java.util.Collections;
2727
import java.util.LinkedHashMap;
28+
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Objects;
3031
import java.util.Set;
@@ -86,7 +87,7 @@ static MergedAnnotations createMergedAnnotations(String className, RuntimeVisibl
8687
return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
8788
}
8889
case AnnotationValue.OfClass classValue -> {
89-
return fromTypeDescriptor(classValue.className().stringValue());
90+
return loadClass(classValue.className().stringValue(), classLoader);
9091
}
9192
case AnnotationValue.OfEnum enumValue -> {
9293
return parseEnum(enumValue, classLoader);
@@ -103,6 +104,16 @@ private static String fromTypeDescriptor(String descriptor) {
103104
classDesc.packageName() + "." + classDesc.displayName();
104105
}
105106

107+
private static Class<?> loadClass(String className, @Nullable ClassLoader classLoader) {
108+
try {
109+
String name = fromTypeDescriptor(className);
110+
return ClassUtils.forName(name, classLoader);
111+
}
112+
catch (ClassNotFoundException ex) {
113+
return Object.class;
114+
}
115+
}
116+
106117
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
107118
if (arrayValue.values().isEmpty()) {
108119
return new Object[0];
@@ -119,10 +130,10 @@ private static Object parseArrayValue(String className, @Nullable ClassLoader cl
119130
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
120131
}
121132
default -> {
122-
Object firstResolvedValue = readAnnotationValue(className, arrayValue.values().getFirst(), classLoader);
133+
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
123134
return stream
124135
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
125-
.toArray(s -> (Object[]) Array.newInstance(firstResolvedValue.getClass(), s));
136+
.toArray(s -> (Object[]) Array.newInstance(arrayElementType, s));
126137
}
127138
}
128139
}
@@ -139,6 +150,28 @@ private static Object parseArrayValue(String className, @Nullable ClassLoader cl
139150
}
140151
}
141152

153+
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
154+
AnnotationValue firstValue = values.getFirst();
155+
switch (firstValue) {
156+
case AnnotationValue.OfConstant constantValue -> {
157+
return constantValue.resolvedValue().getClass();
158+
}
159+
case AnnotationValue.OfAnnotation _ -> {
160+
return MergedAnnotation.class;
161+
}
162+
case AnnotationValue.OfClass _ -> {
163+
return Class.class;
164+
}
165+
case AnnotationValue.OfEnum enumValue -> {
166+
return loadClass(enumValue.className().stringValue(), classLoader);
167+
}
168+
default -> {
169+
return Object.class;
170+
}
171+
}
172+
}
173+
174+
142175
record Source(Annotation entryName) {
143176

144177
}

spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,11 @@ void getAllAnnotationAttributesReturnsAllAttributes() {
290290
void getComplexAttributeTypesReturnsAll() {
291291
MultiValueMap<String, Object> attributes =
292292
get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
293-
assertThat(attributes).containsOnlyKeys("names", "count", "type", "subAnnotation");
293+
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
294294
assertThat(attributes.get("names")).hasSize(1);
295295
assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"});
296-
assertThat(attributes.get("count")).containsExactlyInAnyOrder(TestEnum.ONE);
297-
assertThat(attributes.get("type")).containsExactlyInAnyOrder(TestEnum.class);
296+
assertThat(attributes.get("count").get(0)).isEqualTo(new TestEnum[]{TestEnum.ONE, TestEnum.TWO});
297+
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{TestEnum.class});
298298
assertThat(attributes.get("subAnnotation")).hasSize(1);
299299
}
300300

@@ -312,8 +312,8 @@ void getComplexAttributeTypesReturnsAllWithKotlinMetadata() {
312312
void getAnnotationAttributeIntType() {
313313
MultiValueMap<String, Object> attributes =
314314
get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
315-
assertThat(attributes).containsOnlyKeys("names", "count", "type", "subAnnotation");
316-
assertThat(attributes.get("type")).contains(int.class);
315+
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
316+
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{int.class});
317317
}
318318

319319
@Test
@@ -454,13 +454,13 @@ public static class WithMetaAnnotationAttributes {
454454
}
455455

456456

457-
@ComplexAttributes(names = {"first", "second"}, count = TestEnum.ONE,
458-
type = TestEnum.class, subAnnotation = @SubAnnotation(name="spring"))
457+
@ComplexAttributes(names = {"first", "second"}, count = {TestEnum.ONE, TestEnum.TWO},
458+
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"))
459459
@Metadata(mv = {42})
460460
public static class WithComplexAttributeTypes {
461461
}
462462

463-
@ComplexAttributes(names = "void", count = TestEnum.ONE, type = int.class,
463+
@ComplexAttributes(names = "void", count = TestEnum.ONE, types = int.class,
464464
subAnnotation = @SubAnnotation(name="spring"))
465465
public static class WithIntType {
466466

@@ -471,9 +471,9 @@ public static class WithIntType {
471471

472472
String[] names();
473473

474-
TestEnum count();
474+
TestEnum[] count();
475475

476-
Class<?> type();
476+
Class<?>[] types();
477477

478478
SubAnnotation subAnnotation();
479479
}
@@ -484,7 +484,15 @@ public static class WithIntType {
484484
}
485485

486486
public enum TestEnum {
487-
ONE, TWO, THREE
487+
ONE {
488+
489+
},
490+
TWO {
491+
492+
},
493+
THREE {
494+
495+
}
488496
}
489497

490498
@RepeatableAnnotation(name = "first")

0 commit comments

Comments
 (0)