Ensure direct @PropertySource annotations override meta-annotations

Prior to this commit, there was an issue with the semantics of property
source overrides. Specifically, a @PropertySource annotation present as
a meta-annotation on a @Configuration class was registered with higher
precedence than a @PropertySource annotation declared closer to (or
directly on) the @Configuration class. Consequently, there was no way
for a "local" @PropertySource annotation to override properties
registered via @PropertySource as a meta-annotation.

This commit addresses this issue by introducing a new overloaded
getMergedRepeatableAnnotationAttributes() variant in
AnnotatedTypeMetadata that allows the caller to supply a
sortByReversedMetaDistance flag. When set to `true`, the annotation
search results will be sorted in reversed order based on each
annotation's meta distance, which effectively orders meta-annotations
before annotations that are declared directly on the underlying element.

ConfigurationClassParser and AnnotationConfigUtils have been updated to
use this new repeatable annotation search method for @PropertySource.

Closes gh-31074
This commit is contained in:
Sam Brannen
2023-08-18 16:23:35 +02:00
parent 285c92bb03
commit 74130d007b
5 changed files with 87 additions and 4 deletions

View File

@@ -263,6 +263,20 @@ class AnnotationMetadataTests {
assertRepeatableAnnotations(metadata);
}
@Test // gh-31074
void multipleComposedRepeatableAnnotationsSortedByReversedMetaDistanceUsingStandardAnnotationMetadata() {
AnnotationMetadata metadata = AnnotationMetadata.introspect(MultipleComposedRepeatableAnnotationsClass.class);
assertRepeatableAnnotationsSortedByReversedMetaDistance(metadata);
}
@Test // gh-31074
void multipleComposedRepeatableAnnotationsSortedByReversedMetaDistanceUsingSimpleAnnotationMetadata() throws Exception {
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(MultipleComposedRepeatableAnnotationsClass.class.getName());
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
assertRepeatableAnnotationsSortedByReversedMetaDistance(metadata);
}
@Test // gh-31041
void multipleRepeatableAnnotationsInContainersUsingStandardAnnotationMetadata() {
AnnotationMetadata metadata = AnnotationMetadata.introspect(MultipleRepeatableAnnotationsInContainersClass.class);
@@ -318,6 +332,18 @@ class AnnotationMetadataTests {
.containsExactly("A", "B", "C", "D");
}
private static void assertRepeatableAnnotationsSortedByReversedMetaDistance(AnnotationMetadata metadata) {
// Note: although the real @ComponentScan annotation is not looked up using
// "sortByReversedMetaDistance" semantics, we can still use @TestComponentScan
// to verify the expected behavior.
Set<AnnotationAttributes> attributesSet =
metadata.getMergedRepeatableAnnotationAttributes(TestComponentScan.class, TestComponentScans.class, false, true);
assertThat(attributesSet.stream().map(attributes -> attributes.getStringArray("value")).flatMap(Arrays::stream))
.containsExactly("C", "D", "A", "B");
assertThat(attributesSet.stream().map(attributes -> attributes.getStringArray("basePackages")).flatMap(Arrays::stream))
.containsExactly("C", "D", "A", "B");
}
private static void assertRepeatableAnnotations(Set<TestComponentScan> annotations) {
assertThat(annotations.stream().map(TestComponentScan::value).flatMap(Arrays::stream))
.containsExactly("A", "B", "C", "D");