Browse Source

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
pull/35392/head
Brian Clozel 7 months ago
parent
commit
2b7f88ee44
  1. 39
      spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java
  2. 30
      spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java

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

@ -25,6 +25,7 @@ import java.lang.constant.ClassDesc;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -86,7 +87,7 @@ abstract class ClassFileAnnotationMetadata {
return createMergedAnnotation(className, annotationValue.annotation(), classLoader); return createMergedAnnotation(className, annotationValue.annotation(), classLoader);
} }
case AnnotationValue.OfClass classValue -> { case AnnotationValue.OfClass classValue -> {
return fromTypeDescriptor(classValue.className().stringValue()); return loadClass(classValue.className().stringValue(), classLoader);
} }
case AnnotationValue.OfEnum enumValue -> { case AnnotationValue.OfEnum enumValue -> {
return parseEnum(enumValue, classLoader); return parseEnum(enumValue, classLoader);
@ -103,6 +104,16 @@ abstract class ClassFileAnnotationMetadata {
classDesc.packageName() + "." + classDesc.displayName(); 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) { private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
if (arrayValue.values().isEmpty()) { if (arrayValue.values().isEmpty()) {
return new Object[0]; return new Object[0];
@ -119,10 +130,10 @@ abstract class ClassFileAnnotationMetadata {
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray(); return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
} }
default -> { default -> {
Object firstResolvedValue = readAnnotationValue(className, arrayValue.values().getFirst(), classLoader); Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
return stream return stream
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader)) .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) { record Source(Annotation entryName) {
} }

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

@ -290,11 +290,11 @@ public abstract class AbstractAnnotationMetadataTests {
void getComplexAttributeTypesReturnsAll() { void getComplexAttributeTypesReturnsAll() {
MultiValueMap<String, Object> attributes = MultiValueMap<String, Object> attributes =
get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName()); 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")).hasSize(1);
assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"}); assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"});
assertThat(attributes.get("count")).containsExactlyInAnyOrder(TestEnum.ONE); assertThat(attributes.get("count").get(0)).isEqualTo(new TestEnum[]{TestEnum.ONE, TestEnum.TWO});
assertThat(attributes.get("type")).containsExactlyInAnyOrder(TestEnum.class); assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{TestEnum.class});
assertThat(attributes.get("subAnnotation")).hasSize(1); assertThat(attributes.get("subAnnotation")).hasSize(1);
} }
@ -312,8 +312,8 @@ public abstract class AbstractAnnotationMetadataTests {
void getAnnotationAttributeIntType() { void getAnnotationAttributeIntType() {
MultiValueMap<String, Object> attributes = MultiValueMap<String, Object> attributes =
get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName()); get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
assertThat(attributes).containsOnlyKeys("names", "count", "type", "subAnnotation"); assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
assertThat(attributes.get("type")).contains(int.class); assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{int.class});
} }
@Test @Test
@ -454,13 +454,13 @@ public abstract class AbstractAnnotationMetadataTests {
} }
@ComplexAttributes(names = {"first", "second"}, count = TestEnum.ONE, @ComplexAttributes(names = {"first", "second"}, count = {TestEnum.ONE, TestEnum.TWO},
type = TestEnum.class, subAnnotation = @SubAnnotation(name="spring")) types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"))
@Metadata(mv = {42}) @Metadata(mv = {42})
public static class WithComplexAttributeTypes { 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")) subAnnotation = @SubAnnotation(name="spring"))
public static class WithIntType { public static class WithIntType {
@ -471,9 +471,9 @@ public abstract class AbstractAnnotationMetadataTests {
String[] names(); String[] names();
TestEnum count(); TestEnum[] count();
Class<?> type(); Class<?>[] types();
SubAnnotation subAnnotation(); SubAnnotation subAnnotation();
} }
@ -484,7 +484,15 @@ public abstract class AbstractAnnotationMetadataTests {
} }
public enum TestEnum { public enum TestEnum {
ONE, TWO, THREE ONE {
},
TWO {
},
THREE {
}
} }
@RepeatableAnnotation(name = "first") @RepeatableAnnotation(name = "first")

Loading…
Cancel
Save