Use mapped annotation values when resolving mirrors
Update `TypeMappedAnnotation` mirror resolution logic so that mapped
annotation values are also considered. Prior to this commit, mirrors
in more complex meta-annotation scenarios would not resolve correctly.
Specifically, given the following:
@interface TestAliased {
@AliasFor(attribute = "qualifier")
String value() default "";
@AliasFor(attribute = "value")
String qualifier() default "";
}
@TestAliased
@interface TestMetaAliased {
@AliasFor(annotation = Aliased.class, attribute = "value")
String value() default "";
}
@TestMetaAliased("test")
@interface TestComposed {
}
A merged `@TestAliased` annotation obtained from a `@TestComposed`
root annotation would return a `value` and `qualifier` of "".
This was because the "value" and "qualifier" mirrors needed to be
resolved against the `@TestMetaAliased` meta-annotation. They cannot be
resolved against the declared `@TestComposed` annotation because it
does not have any attributes. Our previous tests only covered a single
depth scenario where `@TestMetaAliased` was used directly on the
annotated element.
Closes gh-23767
Co-authored-by: Sam Brannen <sbrannen@pivotal.io>
This commit is contained in:
@@ -673,6 +673,62 @@ class AnnotatedElementUtilsTests {
|
||||
assertComponentScanAttributes(ComponentScanWithBasePackagesAndValueAliasClass.class, "com.example.app.test");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/23767">#23767</a>
|
||||
*/
|
||||
@Test
|
||||
void findMergedAnnotationAttributesOnMethodWithComposedMetaTransactionalAnnotation() throws Exception {
|
||||
Method method = getClass().getDeclaredMethod("composedTransactionalMethod");
|
||||
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(method, AliasedTransactional.class);
|
||||
assertThat(attributes).as("Should find @AliasedTransactional on " + method).isNotNull();
|
||||
assertThat(attributes.getString("value")).as("TX qualifier for " + method).isEqualTo("anotherTransactionManager");
|
||||
assertThat(attributes.getString("qualifier")).as("TX qualifier for " + method).isEqualTo("anotherTransactionManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/23767">#23767</a>
|
||||
*/
|
||||
@Test
|
||||
void findMergedAnnotationOnMethodWithComposedMetaTransactionalAnnotation() throws Exception {
|
||||
Method method = getClass().getDeclaredMethod("composedTransactionalMethod");
|
||||
|
||||
AliasedTransactional annotation = findMergedAnnotation(method, AliasedTransactional.class);
|
||||
assertThat(annotation).as("Should find @AliasedTransactional on " + method).isNotNull();
|
||||
assertThat(annotation.value()).as("TX qualifier for " + method).isEqualTo("anotherTransactionManager");
|
||||
assertThat(annotation.qualifier()).as("TX qualifier for " + method).isEqualTo("anotherTransactionManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/23767">#23767</a>
|
||||
*/
|
||||
@Test
|
||||
void findMergedAnnotationAttributesOnClassWithComposedMetaTransactionalAnnotation() throws Exception {
|
||||
Class<?> clazz = ComposedTransactionalClass.class;
|
||||
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(clazz, AliasedTransactional.class);
|
||||
assertThat(attributes).as("Should find @AliasedTransactional on " + clazz).isNotNull();
|
||||
assertThat(attributes.getString("value")).as("TX qualifier for " + clazz).isEqualTo("anotherTransactionManager");
|
||||
assertThat(attributes.getString("qualifier")).as("TX qualifier for " + clazz).isEqualTo("anotherTransactionManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2.1
|
||||
* @see <a href="https://github.com/spring-projects/spring-framework/issues/23767">#23767</a>
|
||||
*/
|
||||
@Test
|
||||
void findMergedAnnotationOnClassWithComposedMetaTransactionalAnnotation() throws Exception {
|
||||
Class<?> clazz = ComposedTransactionalClass.class;
|
||||
|
||||
AliasedTransactional annotation = findMergedAnnotation(clazz, AliasedTransactional.class);
|
||||
assertThat(annotation).as("Should find @AliasedTransactional on " + clazz).isNotNull();
|
||||
assertThat(annotation.value()).as("TX qualifier for " + clazz).isEqualTo("anotherTransactionManager");
|
||||
assertThat(annotation.qualifier()).as("TX qualifier for " + clazz).isEqualTo("anotherTransactionManager");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() {
|
||||
assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test");
|
||||
@@ -882,6 +938,21 @@ class AnnotatedElementUtilsTests {
|
||||
String qualifier() default "";
|
||||
}
|
||||
|
||||
@AliasedTransactional
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@interface MyAliasedTransactional {
|
||||
|
||||
@AliasFor(annotation = AliasedTransactional.class, attribute = "value")
|
||||
String value() default "defaultTransactionManager";
|
||||
}
|
||||
|
||||
@MyAliasedTransactional("anotherTransactionManager")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@interface ComposedMyAliasedTransactional {
|
||||
}
|
||||
|
||||
@Transactional(qualifier = "composed1")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -1185,6 +1256,14 @@ class AnnotatedElementUtilsTests {
|
||||
static class AliasedTransactionalComponentClass {
|
||||
}
|
||||
|
||||
@ComposedMyAliasedTransactional
|
||||
void composedTransactionalMethod() {
|
||||
}
|
||||
|
||||
@ComposedMyAliasedTransactional
|
||||
static class ComposedTransactionalClass {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
static class ClassWithInheritedAnnotation {
|
||||
}
|
||||
|
||||
@@ -587,6 +587,24 @@ class MergedAnnotationsTests {
|
||||
assertThat(synthesizedAnnotation.qualifier()).isEqualTo(qualifier);
|
||||
}
|
||||
|
||||
@Test // gh-23767
|
||||
void getWithTypeHierarchyFromClassWithComposedMetaTransactionalAnnotation() {
|
||||
MergedAnnotation<AliasedTransactional> mergedAnnotation = MergedAnnotations.from(
|
||||
ComposedTransactionalClass.class, SearchStrategy.TYPE_HIERARCHY).get(
|
||||
AliasedTransactional.class);
|
||||
assertThat(mergedAnnotation.getString("value")).isEqualTo("anotherTransactionManager");
|
||||
assertThat(mergedAnnotation.getString("qualifier")).isEqualTo("anotherTransactionManager");
|
||||
}
|
||||
|
||||
@Test // gh-23767
|
||||
void getWithTypeHierarchyFromClassWithMetaMetaAliasedTransactional() {
|
||||
MergedAnnotation<AliasedTransactional> mergedAnnotation = MergedAnnotations.from(
|
||||
MetaMetaAliasedTransactionalClass.class, SearchStrategy.TYPE_HIERARCHY).get(
|
||||
AliasedTransactional.class);
|
||||
assertThat(mergedAnnotation.getString("value")).isEqualTo("meta");
|
||||
assertThat(mergedAnnotation.getString("qualifier")).isEqualTo("meta");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithTypeHierarchyFromClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
|
||||
MergedAnnotation<?> annotation = testGetWithTypeHierarchy(
|
||||
@@ -2200,6 +2218,38 @@ class MergedAnnotationsTests {
|
||||
@interface AliasedTransactionalComponent {
|
||||
}
|
||||
|
||||
@AliasedTransactional
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MyAliasedTransactional {
|
||||
|
||||
@AliasFor(annotation = AliasedTransactional.class, attribute = "value")
|
||||
String value() default "defaultTransactionManager";
|
||||
}
|
||||
|
||||
@MyAliasedTransactional("anotherTransactionManager")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@interface ComposedMyAliasedTransactional {
|
||||
}
|
||||
|
||||
@ComposedMyAliasedTransactional
|
||||
static class ComposedTransactionalClass {
|
||||
}
|
||||
|
||||
@AliasedTransactional("meta")
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaAliasedTransactional {
|
||||
}
|
||||
|
||||
@MetaAliasedTransactional
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaMetaAliasedTransactional {
|
||||
}
|
||||
|
||||
@MetaMetaAliasedTransactional
|
||||
static class MetaMetaAliasedTransactionalClass {
|
||||
}
|
||||
|
||||
@TxComposedWithOverride
|
||||
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
|
||||
@Transactional(qualifier = "localTxMgr")
|
||||
|
||||
Reference in New Issue
Block a user