diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 99b10db9b7e..b121cbb779a 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.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. @@ -36,6 +36,7 @@ import javax.annotation.meta.When; import jakarta.annotation.Resource; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AnnotationUtilsTests.ExtendsBaseClassWithGenericAnnotatedMethod; @@ -82,6 +83,90 @@ class AnnotatedElementUtilsTests { private static final String TX_NAME = Transactional.class.getName(); + @Nested + class ConventionBasedAnnotationAttributeOverrideTests { + + @Test + void getMergedAnnotationAttributesWithConventionBasedComposedAnnotation() { + Class> element = ConventionBasedComposedContextConfigClass.class; + String name = ContextConfig.class.getName(); + AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name); + + assertThat(attributes).as("Should find @ContextConfig on " + element.getSimpleName()).isNotNull(); + assertThat(attributes.getStringArray("locations")).as("locations").containsExactly("explicitDeclaration"); + assertThat(attributes.getStringArray("value")).as("value").containsExactly("explicitDeclaration"); + + // Verify contracts between utility methods: + assertThat(isAnnotated(element, name)).isTrue(); + } + + /** + * This test should never pass, simply because Spring does not support a hybrid + * approach for annotation attribute overrides with transitive implicit aliases. + * See SPR-13554 for details. + *
Furthermore, if you choose to execute this test, it can fail for either + * the first test class or the second one (with different exceptions), depending + * on the order in which the JVM returns the attribute methods via reflection. + */ + @Disabled("Permanently disabled but left in place for illustrative purposes") + @Test + void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation() { + for (Class> clazz : asList(HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1.class, + HalfConventionBasedAndHalfAliasedComposedContextConfigClassV2.class)) { + getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(clazz); + } + } + + private void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(Class> clazz) { + String name = ContextConfig.class.getName(); + String simpleName = clazz.getSimpleName(); + AnnotationAttributes attributes = getMergedAnnotationAttributes(clazz, name); + + assertThat(attributes).as("Should find @ContextConfig on " + simpleName).isNotNull(); + assertThat(attributes.getStringArray("locations")).as("locations for class [" + simpleName + "]") + .containsExactly("explicitDeclaration"); + assertThat(attributes.getStringArray("value")).as("value for class [" + simpleName + "]") + .containsExactly("explicitDeclaration"); + + // Verify contracts between utility methods: + assertThat(isAnnotated(clazz, name)).isTrue(); + } + + @Test + void getMergedAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() { + Class> element = InvalidConventionBasedComposedContextConfigClass.class; + assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> + getMergedAnnotationAttributes(element, ContextConfig.class)) + .withMessageContaining("Different @AliasFor mirror values for annotation") + .withMessageContaining("attribute 'locations' and its alias 'value'") + .withMessageContaining("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]"); + } + + @Test + void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() { + assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test"); + } + + @Test + void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() { + Class> element = SpringAppConfigClass.class; + ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class); + + assertThat(contextConfig).as("Should find @ContextConfig on " + element).isNotNull(); + assertThat(contextConfig.locations()).as("locations for " + element).isEmpty(); + // 'value' in @SpringAppConfig should not override 'value' in @ContextConfig + assertThat(contextConfig.value()).as("value for " + element).isEmpty(); + assertThat(contextConfig.classes()).as("classes for " + element).containsExactly(Number.class); + } + + @Test + void findMergedAnnotationWithSingleElementOverridingAnArrayViaConvention() throws Exception { + assertWebMapping(WebController.class.getMethod("postMappedWithPathAttribute")); + } + + } + + @Test void getMetaAnnotationTypesOnNonAnnotatedClass() { assertThat(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class).isEmpty()).isTrue(); @@ -363,51 +448,6 @@ class AnnotatedElementUtilsTests { assertThat(isAnnotated(element, name)).isTrue(); } - @Test - void getMergedAnnotationAttributesWithConventionBasedComposedAnnotation() { - Class> element = ConventionBasedComposedContextConfigClass.class; - String name = ContextConfig.class.getName(); - AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name); - - assertThat(attributes).as("Should find @ContextConfig on " + element.getSimpleName()).isNotNull(); - assertThat(attributes.getStringArray("locations")).as("locations").isEqualTo(asArray("explicitDeclaration")); - assertThat(attributes.getStringArray("value")).as("value").isEqualTo(asArray("explicitDeclaration")); - - // Verify contracts between utility methods: - assertThat(isAnnotated(element, name)).isTrue(); - } - - /** - * This test should never pass, simply because Spring does not support a hybrid - * approach for annotation attribute overrides with transitive implicit aliases. - * See SPR-13554 for details. - *
Furthermore, if you choose to execute this test, it can fail for either - * the first test class or the second one (with different exceptions), depending - * on the order in which the JVM returns the attribute methods via reflection. - */ - @Disabled("Permanently disabled but left in place for illustrative purposes") - @Test - void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation() { - for (Class> clazz : asList(HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1.class, - HalfConventionBasedAndHalfAliasedComposedContextConfigClassV2.class)) { - getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(clazz); - } - } - - private void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation(Class> clazz) { - String[] expected = asArray("explicitDeclaration"); - String name = ContextConfig.class.getName(); - String simpleName = clazz.getSimpleName(); - AnnotationAttributes attributes = getMergedAnnotationAttributes(clazz, name); - - assertThat(attributes).as("Should find @ContextConfig on " + simpleName).isNotNull(); - assertThat(attributes.getStringArray("locations")).as("locations for class [" + clazz.getSimpleName() + "]").isEqualTo(expected); - assertThat(attributes.getStringArray("value")).as("value for class [" + clazz.getSimpleName() + "]").isEqualTo(expected); - - // Verify contracts between utility methods: - assertThat(isAnnotated(clazz, name)).isTrue(); - } - @Test void getMergedAnnotationAttributesWithAliasedComposedAnnotation() { Class> element = AliasedComposedContextConfigClass.class; @@ -530,16 +570,6 @@ class AnnotatedElementUtilsTests { assertThat(isAnnotated(element, name)).isTrue(); } - @Test - void getMergedAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() { - Class> element = InvalidConventionBasedComposedContextConfigClass.class; - assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(() -> - getMergedAnnotationAttributes(element, ContextConfig.class)) - .withMessageContaining("Different @AliasFor mirror values for annotation") - .withMessageContaining("attribute 'locations' and its alias 'value'") - .withMessageContaining("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]"); - } - @Test void getMergedAnnotationAttributesWithShadowedAliasComposedAnnotation() { Class> element = ShadowedAliasComposedContextConfigClass.class; @@ -743,11 +773,6 @@ class AnnotatedElementUtilsTests { assertThat(annotation.qualifier()).as("TX qualifier for " + clazz).isEqualTo("anotherTransactionManager"); } - @Test - void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() { - assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test"); - } - @Test void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaAliasFor() { assertComponentScanAttributes(AliasForBasedSinglePackageComponentScanClass.class, "com.example.app.test"); @@ -800,24 +825,6 @@ class AnnotatedElementUtilsTests { assertThat(testPropSource.value()).as("value").isEqualTo(propFiles); } - @Test - void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() { - final String[] EMPTY = new String[0]; - Class> element = SpringAppConfigClass.class; - ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class); - - assertThat(contextConfig).as("Should find @ContextConfig on " + element).isNotNull(); - assertThat(contextConfig.locations()).as("locations for " + element).isEqualTo(EMPTY); - // 'value' in @SpringAppConfig should not override 'value' in @ContextConfig - assertThat(contextConfig.value()).as("value for " + element).isEqualTo(EMPTY); - assertThat(contextConfig.classes()).as("classes for " + element).isEqualTo(new Class>[] {Number.class}); - } - - @Test - void findMergedAnnotationWithSingleElementOverridingAnArrayViaConvention() throws Exception { - assertWebMapping(WebController.class.getMethod("postMappedWithPathAttribute")); - } - @Test void findMergedAnnotationWithSingleElementOverridingAnArrayViaAliasFor() throws Exception { assertWebMapping(WebController.class.getMethod("getMappedWithValueAttribute")); 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 063d00440f6..7b0c51d00fe 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 @@ -232,7 +232,7 @@ class MergedAnnotationsTests { } @Test - void withInheritedAnnotationsFromHalfConventionBasedAndHalfAliasedComposedAnnotation2() { + void getWithInheritedAnnotationsFromHalfConventionBasedAndHalfAliasedComposedAnnotation2() { // SPR-13554: convention mapping mixed with AliasFor annotations // locations doesn't apply because it has no AliasFor annotation MergedAnnotation> annotation =