21
21
import java .lang .annotation .Documented ;
22
22
import java .lang .annotation .ElementType ;
23
23
import java .lang .annotation .Inherited ;
24
+ import java .lang .annotation .Repeatable ;
24
25
import java .lang .annotation .Retention ;
25
26
import java .lang .annotation .RetentionPolicy ;
26
27
import java .lang .annotation .Target ;
32
33
import org .junit .jupiter .api .Test ;
33
34
34
35
import org .springframework .core .annotation .AliasFor ;
36
+ import org .springframework .core .annotation .AnnotatedElementUtils ;
35
37
import org .springframework .core .annotation .AnnotationAttributes ;
36
38
import org .springframework .core .testfixture .stereotype .Component ;
37
39
import org .springframework .core .type .classreading .MetadataReader ;
@@ -247,6 +249,82 @@ void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingSimple
247
249
assertMultipleAnnotationsWithIdenticalAttributeNames (metadata );
248
250
}
249
251
252
+ @ Test // gh-31041
253
+ void multipleComposedRepeatableAnnotationsUsingStandardAnnotationMetadata () {
254
+ AnnotationMetadata metadata = AnnotationMetadata .introspect (MultipleComposedRepeatableAnnotationsClass .class );
255
+ assertRepeatableAnnotations (metadata );
256
+ }
257
+
258
+ @ Test // gh-31041
259
+ void multipleComposedRepeatableAnnotationsUsingSimpleAnnotationMetadata () throws Exception {
260
+ MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory ();
261
+ MetadataReader metadataReader = metadataReaderFactory .getMetadataReader (MultipleComposedRepeatableAnnotationsClass .class .getName ());
262
+ AnnotationMetadata metadata = metadataReader .getAnnotationMetadata ();
263
+ assertRepeatableAnnotations (metadata );
264
+ }
265
+
266
+ @ Test // gh-31041
267
+ void multipleRepeatableAnnotationsInContainersUsingStandardAnnotationMetadata () {
268
+ AnnotationMetadata metadata = AnnotationMetadata .introspect (MultipleRepeatableAnnotationsInContainersClass .class );
269
+ assertRepeatableAnnotations (metadata );
270
+ }
271
+
272
+ @ Test // gh-31041
273
+ void multipleRepeatableAnnotationsInContainersUsingSimpleAnnotationMetadata () throws Exception {
274
+ MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory ();
275
+ MetadataReader metadataReader = metadataReaderFactory .getMetadataReader (MultipleRepeatableAnnotationsInContainersClass .class .getName ());
276
+ AnnotationMetadata metadata = metadataReader .getAnnotationMetadata ();
277
+ assertRepeatableAnnotations (metadata );
278
+ }
279
+
280
+ /**
281
+ * Tests {@code AnnotatedElementUtils#getMergedRepeatableAnnotations()} variants to ensure that
282
+ * {@link AnnotationMetadata#getMergedRepeatableAnnotationAttributes(Class, Class, boolean)}
283
+ * behaves the same.
284
+ */
285
+ @ Test // gh-31041
286
+ void multipleComposedRepeatableAnnotationsUsingAnnotatedElementUtils () throws Exception {
287
+ Class <?> element = MultipleComposedRepeatableAnnotationsClass .class ;
288
+
289
+ Set <TestComponentScan > annotations = AnnotatedElementUtils .getMergedRepeatableAnnotations (element , TestComponentScan .class );
290
+ assertRepeatableAnnotations (annotations );
291
+
292
+ annotations = AnnotatedElementUtils .getMergedRepeatableAnnotations (element , TestComponentScan .class , TestComponentScans .class );
293
+ assertRepeatableAnnotations (annotations );
294
+ }
295
+
296
+ /**
297
+ * Tests {@code AnnotatedElementUtils#getMergedRepeatableAnnotations()} variants to ensure that
298
+ * {@link AnnotationMetadata#getMergedRepeatableAnnotationAttributes(Class, Class, boolean)}
299
+ * behaves the same.
300
+ */
301
+ @ Test // gh-31041
302
+ void multipleRepeatableAnnotationsInContainersUsingAnnotatedElementUtils () throws Exception {
303
+ Class <?> element = MultipleRepeatableAnnotationsInContainersClass .class ;
304
+
305
+ Set <TestComponentScan > annotations = AnnotatedElementUtils .getMergedRepeatableAnnotations (element , TestComponentScan .class );
306
+ assertRepeatableAnnotations (annotations );
307
+
308
+ annotations = AnnotatedElementUtils .getMergedRepeatableAnnotations (element , TestComponentScan .class , TestComponentScans .class );
309
+ assertRepeatableAnnotations (annotations );
310
+ }
311
+
312
+ private static void assertRepeatableAnnotations (AnnotationMetadata metadata ) {
313
+ Set <AnnotationAttributes > attributesSet =
314
+ metadata .getMergedRepeatableAnnotationAttributes (TestComponentScan .class , TestComponentScans .class , false );
315
+ assertThat (attributesSet .stream ().map (attributes -> attributes .getStringArray ("value" )).flatMap (Arrays ::stream ))
316
+ .containsExactly ("A" , "B" , "C" , "D" );
317
+ assertThat (attributesSet .stream ().map (attributes -> attributes .getStringArray ("basePackages" )).flatMap (Arrays ::stream ))
318
+ .containsExactly ("A" , "B" , "C" , "D" );
319
+ }
320
+
321
+ private static void assertRepeatableAnnotations (Set <TestComponentScan > annotations ) {
322
+ assertThat (annotations .stream ().map (TestComponentScan ::value ).flatMap (Arrays ::stream ))
323
+ .containsExactly ("A" , "B" , "C" , "D" );
324
+ assertThat (annotations .stream ().map (TestComponentScan ::basePackages ).flatMap (Arrays ::stream ))
325
+ .containsExactly ("A" , "B" , "C" , "D" );
326
+ }
327
+
250
328
@ Test
251
329
void inheritedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata () {
252
330
AnnotationMetadata metadata = AnnotationMetadata .introspect (NamedComposedAnnotationExtended .class );
@@ -534,6 +612,14 @@ private static class AnnotatedComponentSubClass extends AnnotatedComponent {
534
612
535
613
@ Retention (RetentionPolicy .RUNTIME )
536
614
@ Target (ElementType .TYPE )
615
+ public @interface TestComponentScans {
616
+
617
+ TestComponentScan [] value ();
618
+ }
619
+
620
+ @ Retention (RetentionPolicy .RUNTIME )
621
+ @ Target (ElementType .TYPE )
622
+ @ Repeatable (TestComponentScans .class )
537
623
public @interface TestComponentScan {
538
624
539
625
@ AliasFor ("basePackages" )
@@ -560,6 +646,40 @@ private static class AnnotatedComponentSubClass extends AnnotatedComponent {
560
646
public static class ComposedConfigurationWithAttributeOverridesClass {
561
647
}
562
648
649
+ @ Retention (RetentionPolicy .RUNTIME )
650
+ @ Target (ElementType .TYPE )
651
+ @ TestComponentScan ("C" )
652
+ public @interface ScanPackageC {
653
+ }
654
+
655
+ @ Retention (RetentionPolicy .RUNTIME )
656
+ @ Target (ElementType .TYPE )
657
+ @ TestComponentScan ("D" )
658
+ public @interface ScanPackageD {
659
+ }
660
+
661
+ @ Retention (RetentionPolicy .RUNTIME )
662
+ @ Target (ElementType .TYPE )
663
+ @ TestComponentScans ({
664
+ @ TestComponentScan ("C" ),
665
+ @ TestComponentScan ("D" )
666
+ })
667
+ public @interface ScanPackagesCandD {
668
+ }
669
+
670
+ @ TestComponentScan ("A" )
671
+ @ ScanPackageC
672
+ @ ScanPackageD
673
+ @ TestComponentScan ("B" )
674
+ static class MultipleComposedRepeatableAnnotationsClass {
675
+ }
676
+
677
+ @ TestComponentScan ("A" )
678
+ @ ScanPackagesCandD
679
+ @ TestComponentScans (@ TestComponentScan ("B" ))
680
+ static class MultipleRepeatableAnnotationsInContainersClass {
681
+ }
682
+
563
683
@ Retention (RetentionPolicy .RUNTIME )
564
684
@ Target (ElementType .TYPE )
565
685
public @interface NamedAnnotation1 {
0 commit comments