Browse Source

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>
pull/23847/head
Phillip Webb 5 years ago
parent
commit
ad543dbee3
  1. 8
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java
  2. 12
      spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java
  3. 79
      spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
  4. 50
      spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

8
spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java

@ -444,15 +444,21 @@ final class AnnotationTypeMapping { @@ -444,15 +444,21 @@ final class AnnotationTypeMapping {
* convention and alias based mapping rules. For root mappings, this method
* will always return {@code null}.
* @param attributeIndex the attribute index of the source attribute
* @param metaAnnotationsOnly if only meta annotations should be considered.
* If this parameter is {@code false} then aliases within the annotation will
* not be excluded.
* @return the mapped annotation value, or {@code null}
*/
@Nullable
Object getMappedAnnotationValue(int attributeIndex) {
Object getMappedAnnotationValue(int attributeIndex, boolean metaAnnotationsOnly) {
int mapped = this.annotationValueMappings[attributeIndex];
if (mapped == -1) {
return null;
}
AnnotationTypeMapping source = this.annotationValueSource[attributeIndex];
if(source == this && metaAnnotationsOnly) {
return null;
}
return ReflectionUtils.invokeMethod(source.attributes.get(mapped), source.annotation);
}

12
spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

@ -436,11 +436,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn @@ -436,11 +436,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private Object getValueFromMetaAnnotation(int attributeIndex,
boolean forMirrorResolution) {
if (this.useMergedValues && !forMirrorResolution) {
return this.mapping.getMappedAnnotationValue(attributeIndex);
Object value = null;
if (this.useMergedValues || forMirrorResolution) {
value = this.mapping.getMappedAnnotationValue(attributeIndex, forMirrorResolution);
}
Method attribute = this.mapping.getAttributes().get(attributeIndex);
return ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
if (value == null) {
Method attribute = mapping.getAttributes().get(attributeIndex);
value = ReflectionUtils.invokeMethod(attribute, mapping.getAnnotation());
}
return value;
}
@Nullable

79
spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

@ -673,6 +673,62 @@ class AnnotatedElementUtilsTests { @@ -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 { @@ -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 { @@ -1185,6 +1256,14 @@ class AnnotatedElementUtilsTests {
static class AliasedTransactionalComponentClass {
}
@ComposedMyAliasedTransactional
void composedTransactionalMethod() {
}
@ComposedMyAliasedTransactional
static class ComposedTransactionalClass {
}
@Transactional
static class ClassWithInheritedAnnotation {
}

50
spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

@ -587,6 +587,24 @@ class MergedAnnotationsTests { @@ -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 { @@ -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")

Loading…
Cancel
Save