diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java b/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java index 88d7d78470b3..2a875b04767a 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AliasFor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -173,7 +173,7 @@ * @author Sam Brannen * @since 4.2 * @see MergedAnnotations - * @see SynthesizedAnnotation + * @see AnnotationUtils#isSynthesizedAnnotation(Annotation) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationConfigurationException.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationConfigurationException.java index 4f4839d3e57e..d6744d08eb1a 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationConfigurationException.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationConfigurationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ * @author Sam Brannen * @since 4.2 * @see AnnotationUtils - * @see SynthesizedAnnotation + * @see AnnotationUtils#isSynthesizedAnnotation(java.lang.annotation.Annotation) */ @SuppressWarnings("serial") public class AnnotationConfigurationException extends NestedRuntimeException { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 40e206c70512..11eb589aa583 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1188,7 +1188,7 @@ public static Object getDefaultValue( public static A synthesizeAnnotation( A annotation, @Nullable AnnotatedElement annotatedElement) { - if (annotation instanceof SynthesizedAnnotation || AnnotationFilter.PLAIN.matches(annotation)) { + if (isSynthesizedAnnotation(annotation) || AnnotationFilter.PLAIN.matches(annotation)) { return annotation; } return MergedAnnotation.from(annotatedElement, annotation).synthesize(); @@ -1282,6 +1282,18 @@ static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Annotate return synthesized; } + /** + * Determine if the supplied {@link Annotation} has been synthesized + * by Spring (i.e. wrapped in a dynamic proxy) with additional functionality + * such as attribute alias handling. + * @param annotation the annotation to check + * @return {@code true} if the supplied annotation is a synthesized annotation + * @since 5.3.23 + */ + public static boolean isSynthesizedAnnotation(@Nullable Annotation annotation) { + return (annotation instanceof SynthesizedAnnotation); + } + /** * Clear the internal annotation metadata cache. * @since 4.3.15 diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationBackCompatibilityTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationBackCompatibilityTests.java index 04c3b7b8366a..9ca47d7f6351 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationBackCompatibilityTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationBackCompatibilityTests.java @@ -45,7 +45,7 @@ void multiplRoutesToMetaAnnotation() { @Test void defaultValue() { DefaultValueAnnotation synthesized = MergedAnnotations.from(WithDefaultValue.class).get(DefaultValueAnnotation.class).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertThat(AnnotationUtils.isSynthesizedAnnotation(synthesized)).as("synthesized annotation").isTrue(); Object defaultValue = AnnotationUtils.getDefaultValue(synthesized, "enumValue"); assertThat(defaultValue).isEqualTo(TestEnum.ONE); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java index 0aaeb5732c1a..974917e490d5 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ void toAnnotationSetCollectsLinkedHashSetWithSynthesizedAnnotations() { MergedAnnotationCollectors.toAnnotationSet()); assertThat(set).isInstanceOf(LinkedHashSet.class).flatExtracting( TestAnnotation::value).containsExactly("a", "b", "c"); - assertThat(set).allMatch(SynthesizedAnnotation.class::isInstance); + assertThat(set).allMatch(AnnotationUtils::isSynthesizedAnnotation); } @Test @@ -55,7 +55,7 @@ void toAnnotationArrayCollectsAnnotationArrayWithSynthesizedAnnotations() { assertThat(Arrays.stream(array).map( annotation -> ((TestAnnotation) annotation).value())).containsExactly("a", "b", "c"); - assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance); + assertThat(array).allMatch(AnnotationUtils::isSynthesizedAnnotation); } @Test @@ -64,7 +64,7 @@ void toSuppliedAnnotationArrayCollectsAnnotationArrayWithSynthesizedAnnotations( MergedAnnotationCollectors.toAnnotationArray(TestAnnotation[]::new)); assertThat(Arrays.stream(array).map(TestAnnotation::value)).containsExactly("a", "b", "c"); - assertThat(array).allMatch(SynthesizedAnnotation.class::isInstance); + assertThat(array).allMatch(AnnotationUtils::isSynthesizedAnnotation); } @Test 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 c5672f7c9e24..1e71dcb82dd6 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 @@ -1395,9 +1395,10 @@ void synthesizeAlreadySynthesized() throws Exception { RequestMapping synthesizedWebMapping = MergedAnnotation.from(webMapping).synthesize(); RequestMapping synthesizedAgainWebMapping = MergedAnnotation.from(synthesizedWebMapping).synthesize(); - assertThat(synthesizedWebMapping).isInstanceOf(SynthesizedAnnotation.class); - assertThat(synthesizedAgainWebMapping).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedWebMapping); + assertSynthesized(synthesizedAgainWebMapping); assertThat(synthesizedWebMapping).isEqualTo(synthesizedAgainWebMapping); + assertThat(synthesizedWebMapping).isSameAs(synthesizedAgainWebMapping); assertThat(synthesizedWebMapping.name()).isEqualTo("foo"); assertThat(synthesizedWebMapping.path()).containsExactly("/test"); assertThat(synthesizedWebMapping.value()).containsExactly("/test"); @@ -1412,7 +1413,7 @@ void synthesizeShouldNotSynthesizeNonsynthesizableAnnotations() throws Exception Id synthesizedId = MergedAnnotation.from(id).synthesize(); assertThat(id).isEqualTo(synthesizedId); // It doesn't make sense to synthesize @Id since it declares zero attributes. - assertThat(synthesizedId).isNotInstanceOf(SynthesizedAnnotation.class); + assertNotSynthesized(synthesizedId); assertThat(id).isSameAs(synthesizedId); GeneratedValue generatedValue = method.getAnnotation(GeneratedValue.class); @@ -1420,7 +1421,7 @@ void synthesizeShouldNotSynthesizeNonsynthesizableAnnotations() throws Exception GeneratedValue synthesizedGeneratedValue = MergedAnnotation.from(generatedValue).synthesize(); assertThat(generatedValue).isEqualTo(synthesizedGeneratedValue); // It doesn't make sense to synthesize @GeneratedValue since it declares zero attributes with aliases. - assertThat(synthesizedGeneratedValue).isNotInstanceOf(SynthesizedAnnotation.class); + assertNotSynthesized(synthesizedGeneratedValue); assertThat(generatedValue).isSameAs(synthesizedGeneratedValue); } @@ -1430,19 +1431,19 @@ void synthesizeWhenUsingMergedAnnotationsFromApi() { MergedAnnotations mergedAnnotations = MergedAnnotations.from(directlyAnnotatedField); RootAnnotation rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize(); assertThat(rootAnnotation.flag()).isFalse(); - assertThat(rootAnnotation).isNotInstanceOf(SynthesizedAnnotation.class); + assertNotSynthesized(rootAnnotation); Field metaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaAnnotated"); mergedAnnotations = MergedAnnotations.from(metaAnnotatedField); rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize(); assertThat(rootAnnotation.flag()).isTrue(); - assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(rootAnnotation); Field metaMetaAnnotatedField = ReflectionUtils.findField(DomainType.class, "metaMetaAnnotated"); mergedAnnotations = MergedAnnotations.from(metaMetaAnnotatedField); rootAnnotation = mergedAnnotations.get(RootAnnotation.class).synthesize(); assertThat(rootAnnotation.flag()).isTrue(); - assertThat(rootAnnotation).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(rootAnnotation); } @Test // gh-28704 @@ -1450,10 +1451,10 @@ void synthesizeShouldNotSynthesizeNonsynthesizableAnnotationsWhenUsingMergedAnno MergedAnnotations mergedAnnotations = MergedAnnotations.from(SecurityConfig.class); EnableWebSecurity enableWebSecurity = mergedAnnotations.get(EnableWebSecurity.class).synthesize(); - assertThat(enableWebSecurity).isNotInstanceOf(SynthesizedAnnotation.class); + assertNotSynthesized(enableWebSecurity); EnableGlobalAuthentication enableGlobalAuthentication = mergedAnnotations.get(EnableGlobalAuthentication.class).synthesize(); - assertThat(enableGlobalAuthentication).isNotInstanceOf(SynthesizedAnnotation.class); + assertNotSynthesized(enableGlobalAuthentication); } /** @@ -1472,8 +1473,8 @@ void synthesizeShouldNotResynthesizeAlreadySynthesizedAnnotations() throws Excep RequestMapping synthesizedWebMapping1 = mergedAnnotation1.synthesize(); RequestMapping synthesizedWebMapping2 = MergedAnnotation.from(webMapping).synthesize(); - assertThat(synthesizedWebMapping1).isInstanceOf(SynthesizedAnnotation.class); - assertThat(synthesizedWebMapping2).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedWebMapping1); + assertSynthesized(synthesizedWebMapping2); assertThat(synthesizedWebMapping1).isEqualTo(synthesizedWebMapping2); // Synthesizing an annotation from a different MergedAnnotation results in a different synthesized annotation instance. @@ -1595,14 +1596,11 @@ void synthesizeWithImplicitAliases() throws Exception { testSynthesisWithImplicitAliases(GroovyImplicitAliasesSimpleTestConfigurationClass.class, "groovyScript"); } - private void testSynthesisWithImplicitAliases(Class clazz, String expected) - throws Exception { - ImplicitAliasesTestConfiguration config = clazz.getAnnotation( - ImplicitAliasesTestConfiguration.class); + private void testSynthesisWithImplicitAliases(Class clazz, String expected) throws Exception { + ImplicitAliasesTestConfiguration config = clazz.getAnnotation(ImplicitAliasesTestConfiguration.class); assertThat(config).isNotNull(); - ImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from( - config).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + ImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(config).synthesize(); + assertSynthesized(synthesized); assertThat(synthesized.value()).isEqualTo(expected); assertThat(synthesized.location1()).isEqualTo(expected); assertThat(synthesized.xmlFile()).isEqualTo(expected); @@ -1630,7 +1628,7 @@ private void testSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted( assertThat(config).isNotNull(); ImplicitAliasesWithImpliedAliasNamesOmittedTestConfiguration synthesized = MergedAnnotation.from(config).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesized); assertThat(synthesized.value()).isEqualTo(expected); assertThat(synthesized.location()).isEqualTo(expected); assertThat(synthesized.xmlFile()).isEqualTo(expected); @@ -1642,7 +1640,7 @@ void synthesizeWithImplicitAliasesForAliasPair() throws Exception { ImplicitAliasesForAliasPairTestConfigurationClass.class.getAnnotation( ImplicitAliasesForAliasPairTestConfiguration.class); ImplicitAliasesForAliasPairTestConfiguration synthesized = MergedAnnotation.from(config).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesized); assertThat(synthesized.xmlFile()).isEqualTo("test.xml"); assertThat(synthesized.groovyScript()).isEqualTo("test.xml"); } @@ -1653,7 +1651,7 @@ void synthesizeWithTransitiveImplicitAliases() throws Exception { TransitiveImplicitAliasesTestConfigurationClass.class.getAnnotation( TransitiveImplicitAliasesTestConfiguration.class); TransitiveImplicitAliasesTestConfiguration synthesized = MergedAnnotation.from(config).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesized); assertThat(synthesized.xml()).isEqualTo("test.xml"); assertThat(synthesized.groovy()).isEqualTo("test.xml"); } @@ -1665,7 +1663,7 @@ void synthesizeWithTransitiveImplicitAliasesForAliasPair() throws Exception { TransitiveImplicitAliasesForAliasPairTestConfiguration.class); TransitiveImplicitAliasesForAliasPairTestConfiguration synthesized = MergedAnnotation.from( config).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesized); assertThat(synthesized.xml()).isEqualTo("test.xml"); assertThat(synthesized.groovy()).isEqualTo("test.xml"); } @@ -1724,7 +1722,7 @@ void synthesizeFromMapWithoutAttributeAliases() throws Exception { Map map = Collections.singletonMap("value", "webController"); MergedAnnotation annotation = MergedAnnotation.of(Component.class, map); Component synthesizedComponent = annotation.synthesize(); - assertThat(synthesizedComponent).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedComponent); assertThat(synthesizedComponent.value()).isEqualTo("webController"); } @@ -1745,7 +1743,7 @@ void synthesizeFromMapWithNestedMap() throws Exception { MergedAnnotation annotation = MergedAnnotation.of( ComponentScanSingleFilter.class, map); ComponentScanSingleFilter synthesizedComponentScan = annotation.synthesize(); - assertThat(synthesizedComponentScan).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedComponentScan); assertThat(synthesizedComponentScan.value().pattern()).isEqualTo("newFoo"); } @@ -1769,7 +1767,7 @@ void synthesizeFromMapWithNestedArrayOfMaps() throws Exception { MergedAnnotation annotation = MergedAnnotation.of( ComponentScan.class, map); ComponentScan synthesizedComponentScan = annotation.synthesize(); - assertThat(synthesizedComponentScan).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedComponentScan); assertThat(Arrays.stream(synthesizedComponentScan.excludeFilters()).map( Filter::pattern)).containsExactly("newFoo", "newBar"); } @@ -1888,7 +1886,7 @@ void synthesizeFromAnnotationAttributesWithoutAttributeAliases() throws Exceptio assertThat(component).isNotNull(); Map attributes = MergedAnnotation.from(component).asMap(); Component synthesized = MergedAnnotation.of(Component.class, attributes).synthesize(); - assertThat(synthesized).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesized); assertThat(synthesized).isEqualTo(component); } @@ -2047,7 +2045,7 @@ void synthesizeNonPublicWithAttributeAliasesFromDifferentPackage() throws Except assertThat(annotation).isNotNull(); MergedAnnotation mergedAnnotation = MergedAnnotation.from(annotation); Annotation synthesizedAnnotation = mergedAnnotation.synthesize(); - assertThat(synthesizedAnnotation).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedAnnotation); assertThat(mergedAnnotation.getString("name")).isEqualTo("test"); assertThat(mergedAnnotation.getString("path")).isEqualTo("/test"); assertThat(mergedAnnotation.getString("value")).isEqualTo("/test"); @@ -2058,10 +2056,10 @@ void synthesizeWithArrayOfAnnotations() throws Exception { Hierarchy hierarchy = HierarchyClass.class.getAnnotation(Hierarchy.class); assertThat(hierarchy).isNotNull(); Hierarchy synthesizedHierarchy = MergedAnnotation.from(hierarchy).synthesize(); - assertThat(synthesizedHierarchy).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedHierarchy); TestConfiguration[] configs = synthesizedHierarchy.value(); assertThat(configs).isNotNull(); - assertThat(configs).allMatch(SynthesizedAnnotation.class::isInstance); + assertThat(configs).allMatch(AnnotationUtils::isSynthesizedAnnotation); assertThat(configs).extracting(TestConfiguration::value).containsExactly("A", "B"); assertThat(configs).extracting(TestConfiguration::location).containsExactly("A", "B"); @@ -2082,7 +2080,7 @@ void synthesizeWithArrayOfChars() throws Exception { assertThat(charsContainer).isNotNull(); CharsContainer synthesizedCharsContainer = MergedAnnotation.from( charsContainer).synthesize(); - assertThat(synthesizedCharsContainer).isInstanceOf(SynthesizedAnnotation.class); + assertSynthesized(synthesizedCharsContainer); char[] chars = synthesizedCharsContainer.chars(); assertThat(chars).containsExactly('x', 'y', 'z'); // Alter array returned from synthesized annotation @@ -3682,4 +3680,12 @@ static class ValueAttributeMetaMetaClass { } // @formatter:on + static void assertSynthesized(Annotation annotation) { + assertThat(AnnotationUtils.isSynthesizedAnnotation(annotation)).as("synthesized annotation").isTrue(); + } + + static void assertNotSynthesized(Annotation annotation) { + assertThat(AnnotationUtils.isSynthesizedAnnotation(annotation)).as("synthesized annotation").isFalse(); + } + }