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 67d181818fe..3b1c3853ceb 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 @@ -493,7 +493,9 @@ final class TypeMappedAnnotation extends AbstractMergedAnn private Object adaptForAttribute(Method attribute, Object value) { Class attributeType = ClassUtils.resolvePrimitiveIfNecessary(attribute.getReturnType()); if (attributeType.isArray() && !value.getClass().isArray()) { - Object array = Array.newInstance(value.getClass(), 1); + Object array = ClassUtils.isAssignable(attributeType.getComponentType(), value.getClass()) ? + Array.newInstance(attributeType.getComponentType(), 1) : + Array.newInstance(value.getClass(), 1); Array.set(array, 0, value); return adaptForAttribute(attribute, array); } 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 29e85299707..d179db714ef 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 @@ -596,6 +596,19 @@ class MergedAnnotationsTests { assertThat(annotation.getClassArray("classes")).isEmpty(); } + @Test + void getWithSingleValuePrimitiveAttributesOverridingPrimitiveArraysViaAliasFor() { + MergedAnnotation annotation = MergedAnnotations.from(ComposedPrimitiveSingleValuesClass.class) + .get(PrimitiveArrays.class); + assertThat(annotation.isPresent()).isTrue(); + assertThat(annotation.getIntArray("ints")).containsExactly(42); + assertThat(annotation.getLongArray("longs")).containsExactly(42L); + assertThat(annotation.getBooleanArray("booleans")).containsExactly(true); + assertThat(annotation.getCharArray("chars")).containsExactly('c'); + assertThat(annotation.getDoubleArray("doubles")).containsExactly(4.2d); + assertThat(annotation.synthesize().ints()).containsExactly(42); + } + @Test void getWithInheritedAnnotationsFromShadowedAliasComposedAnnotation() { MergedAnnotation annotation = MergedAnnotations.from( @@ -2301,6 +2314,44 @@ class MergedAnnotationsTests { @interface ComposedTransactionalComponent { } + @Retention(RetentionPolicy.RUNTIME) + @interface PrimitiveArrays { + + int[] ints() default {}; + + long[] longs() default {}; + + boolean[] booleans() default {}; + + char[] chars() default {}; + + double[] doubles() default {}; + } + + @PrimitiveArrays + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedPrimitiveSingleValues { + + @AliasFor(annotation = PrimitiveArrays.class, attribute = "ints") + int anInt() default 0; + + @AliasFor(annotation = PrimitiveArrays.class, attribute = "longs") + long aLong() default 0L; + + @AliasFor(annotation = PrimitiveArrays.class, attribute = "booleans") + boolean aBoolean() default false; + + @AliasFor(annotation = PrimitiveArrays.class, attribute = "chars") + char aChar() default 'x'; + + @AliasFor(annotation = PrimitiveArrays.class, attribute = "doubles") + double aDouble() default 0.0d; + } + + @ComposedPrimitiveSingleValues(anInt = 42, aLong = 42L, aBoolean = true, aChar = 'c', aDouble = 4.2d) + static class ComposedPrimitiveSingleValuesClass { + } + static class NonAnnotatedClass { } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/TypeMappedAnnotationTests.java b/spring-core/src/test/java/org/springframework/core/annotation/TypeMappedAnnotationTests.java index 38b06d20880..6d7104f6e60 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/TypeMappedAnnotationTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/TypeMappedAnnotationTests.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; @@ -43,6 +44,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.throwable; * * @author Phillip Webb * @author Sam Brannen + * @author Chengang Guan */ class TypeMappedAnnotationTests { @@ -110,6 +112,46 @@ class TypeMappedAnnotationTests { assertThat(annotation.getValue("enumValue")).contains(new ExampleEnum[] {}); } + @Test + @SuppressWarnings("unchecked") + void adaptFromSingleValueToArray() { + Map attributes = new HashMap<>(); + attributes.put("stringValue", "s"); + attributes.put("byteValue", (byte) 1); + attributes.put("shortValue", (short) 1); + attributes.put("intValue", 1); + attributes.put("longValue", 1L); + attributes.put("booleanValue", true); + attributes.put("charValue", 'c'); + attributes.put("doubleValue", 1.0d); + attributes.put("floatValue", 1.0f); + attributes.put("classValue", String.class); + attributes.put("enumValue", ExampleEnum.ONE); + attributes.put("annotationValue", TypeMappedAnnotation.of(null, null, + ExplicitMirror.class, Collections.singletonMap("a", "singleValue"))); + + MergedAnnotation annotation = TypeMappedAnnotation.of(null, null, ArrayTypes.class, attributes); + assertThat(annotation.getValue("stringValue")).contains(new String[] {"s"}); + assertThat(annotation.getValue("byteValue")).contains(new byte[] {1}); + assertThat(annotation.getValue("shortValue")).contains(new short[] {1}); + assertThat(annotation.getValue("intValue")).contains(new int[] {1}); + assertThat(annotation.getValue("longValue")).contains(new long[] {1L}); + assertThat(annotation.getValue("booleanValue")).contains(new boolean[] {true}); + assertThat(annotation.getValue("charValue")).contains(new char[] {'c'}); + assertThat(annotation.getValue("doubleValue")).contains(new double[] {1.0d}); + assertThat(annotation.getValue("floatValue")).contains(new float[] {1.0f}); + assertThat(annotation.getValue("classValue")).contains(new Class[] {String.class}); + assertThat(annotation.getValue("enumValue")).contains(new ExampleEnum[] {ExampleEnum.ONE}); + assertThat(annotation.getValue("annotationValue")).hasValueSatisfying(value -> { + assertThat(value.getClass().isArray()).isTrue(); + assertThat(Array.getLength(value)).isEqualTo(1); + Object element = Array.get(value, 0); + MergedAnnotation mirror = (MergedAnnotation) element; + assertThat(mirror.getString("a")).isEqualTo("singleValue"); + assertThat(mirror.getString("b")).isEqualTo("singleValue"); + }); + } + @Test void adaptFromNestedMergedAnnotation() { MergedAnnotation nested = MergedAnnotation.of(NestedAnnotation.class);