diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index 68a67ea7bc9..15c674f9e31 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -329,7 +329,18 @@ final class TypeMappedAnnotation 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()); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 8a9a90e6da1..307f4d020cf 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -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 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 {