Avoid synthesizing annotations unnecessarily

Prior to Spring Framework 5.2, some of our annotation utilities would
not synthesize an annotation if it was already synthesized or not
synthesizable (i.e., did not declare local aliases via @AliasFor and
did not declare attributes that could override attributes in the
meta-annotation hierarchy above the given annotation); however, we lost
most of this functionality with the introduction of the new
MergedAnnotations API.

This commit revises the implementation of createSynthesized() in
TypeMappedAnnotation so that, for invocations of
MergedAnnotation.synthesize() and indirectly for invocations of
AnnotatedElementUtils.findMergedAnnotation(), etc.:

1. An annotation that was previously synthesized will not be
   synthesized again.

2. An annotation that is not "synthesizable" will not be synthesized.

For both of the above use cases, the original annotation is now
returned from createSynthesized().

Closes gh-24861
This commit is contained in:
Sam Brannen 2020-04-20 15:30:48 +02:00
parent 8d31dcaa29
commit 31fa1569c5
2 changed files with 68 additions and 0 deletions

View File

@ -329,7 +329,18 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
@Override
@SuppressWarnings("unchecked")
protected A createSynthesized() {
if (this.rootAttributes instanceof Annotation) {
// Nothing to synthesize?
if (this.resolvedRootMirrors.length == 0 && this.resolvedMirrors.length == 0) {
return (A) this.rootAttributes;
}
// Already synthesized?
if (this.rootAttributes instanceof SynthesizedAnnotation) {
return (A) this.rootAttributes;
}
}
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
}

View File

@ -50,6 +50,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@ -1376,6 +1377,50 @@ class MergedAnnotationsTests {
assertThat(synthesizedWebMapping.value()).containsExactly("/test");
}
@Test
void synthesizeShouldNotSynthesizeNonsynthesizableAnnotations() throws Exception {
Method method = getClass().getDeclaredMethod("getId");
Id id = method.getAnnotation(Id.class);
assertThat(id).isNotNull();
Id synthesizedId = MergedAnnotation.from(id).synthesize();
assertThat(id).isEqualTo(synthesizedId);
// It doesn't make sense to synthesize {@code @Id} since it declares zero attributes.
assertThat(synthesizedId).isNotInstanceOf(SynthesizedAnnotation.class);
assertThat(id).isSameAs(synthesizedId);
}
/**
* If an attempt is made to synthesize an annotation from an annotation instance
* that has already been synthesized, the original synthesized annotation should
* ideally be returned as-is without creating a new proxy instance with the same
* values.
*/
@Test
void synthesizeShouldNotResynthesizeAlreadySynthesizedAnnotations() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
RequestMapping webMapping = method.getAnnotation(RequestMapping.class);
assertThat(webMapping).isNotNull();
MergedAnnotation<RequestMapping> mergedAnnotation1 = MergedAnnotation.from(webMapping);
RequestMapping synthesizedWebMapping1 = mergedAnnotation1.synthesize();
RequestMapping synthesizedWebMapping2 = MergedAnnotation.from(webMapping).synthesize();
assertThat(synthesizedWebMapping1).isInstanceOf(SynthesizedAnnotation.class);
assertThat(synthesizedWebMapping2).isInstanceOf(SynthesizedAnnotation.class);
assertThat(synthesizedWebMapping1).isEqualTo(synthesizedWebMapping2);
// Synthesizing an annotation from a different MergedAnnotation results in a different synthesized annotation instance.
assertThat(synthesizedWebMapping1).isNotSameAs(synthesizedWebMapping2);
// Synthesizing an annotation from the same MergedAnnotation results in the same synthesized annotation instance.
assertThat(synthesizedWebMapping1).isSameAs(mergedAnnotation1.synthesize());
RequestMapping synthesizedAgainWebMapping = MergedAnnotation.from(synthesizedWebMapping1).synthesize();
assertThat(synthesizedWebMapping1).isEqualTo(synthesizedAgainWebMapping);
// Synthesizing an already synthesized annotation results in the original synthesized annotation instance.
assertThat(synthesizedWebMapping1).isSameAs(synthesizedAgainWebMapping);
}
@Test
void synthesizeWhenAliasForIsMissingAttributeDeclaration() throws Exception {
AliasForWithMissingAttributeDeclaration annotation =
@ -2951,6 +2996,18 @@ class MergedAnnotationsTests {
}
}
/**
* Mimics javax.persistence.Id
*/
@Retention(RUNTIME)
@interface Id {
}
@Id
private Long getId() {
return 42L;
}
@Retention(RetentionPolicy.RUNTIME)
@interface TestConfiguration {