diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java index b603cb6df4..139c5b4a2a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java @@ -33,16 +33,10 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.support.SimpleThreadScope; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.test.context.MergedContextConfiguration; -import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation; -import org.springframework.test.context.bean.override.example.ExampleService; -import org.springframework.test.context.bean.override.example.FailingExampleService; -import org.springframework.test.context.bean.override.example.RealExampleService; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.Assert; @@ -56,123 +50,123 @@ import static org.mockito.Mockito.mock; * {@link BeanOverrideRegistrar}. * * @author Simon Baslé + * @author Stephane Nicoll */ class BeanOverrideBeanFactoryPostProcessorTests { @Test void canReplaceExistingBeanDefinitions() { - AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class); - context.register(ReplaceBeans.class); - context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected")); - context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected")); - + AnnotationConfigApplicationContext context = createContext(CaseByNameAndByType.class); + context.registerBean("descriptionBean", String.class, () -> "Original"); + context.registerBean("someInteger", Integer.class, () -> 1); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); + assertThat(context.getBean("someInteger")).isSameAs(42); } @Test - void cannotReplaceIfNoBeanMatching() { - AnnotationConfigApplicationContext context = createContext(ReplaceBeans.class); - context.register(ReplaceBeans.class); - //note we don't register any original bean here + void cannotReplaceIfNoBeanNameMatching() { + AnnotationConfigApplicationContext context = createContext(CaseByName.class); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage("Unable to override bean 'explicit': there is no bean definition " + - "to replace with that name of type org.springframework.test.context.bean.override.example.ExampleService"); + .withMessage("Unable to override bean 'descriptionBean': there is no bean definition " + + "to replace with that name of type java.lang.String"); + } + + @Test + void cannotReplaceIfNoBeanTypeMatching() { + AnnotationConfigApplicationContext context = createContext(CaseByType.class); + + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage("Unable to override bean: no bean definitions of type java.lang.Integer " + + "(as required by annotated field 'CaseByType.counter')"); } @Test void canReplaceExistingBeanDefinitionsWithCreateReplaceStrategy() { - AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class); - context.register(CreateIfOriginalIsMissingBean.class); - context.registerBean("explicit", ExampleService.class, () -> new RealExampleService("unexpected")); - context.registerBean("implicitName", ExampleService.class, () -> new RealExampleService("unexpected")); - + AnnotationConfigApplicationContext context = createContext(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class); + context.register(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class); + context.registerBean("descriptionBean", String.class, () -> "Original"); + context.registerBean("someInteger", Integer.class, () -> 1); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); + assertThat(context.getBean("someInteger")).isEqualTo(42); } @Test void canCreateIfOriginalMissingWithCreateReplaceStrategy() { - AnnotationConfigApplicationContext context = createContext(CreateIfOriginalIsMissingBean.class); - context.register(CreateIfOriginalIsMissingBean.class); - //note we don't register original beans here - + AnnotationConfigApplicationContext context = createContext(CaseByNameAndByTypeWithReplaceOrCreateStrategy.class); context.refresh(); - assertThat(context.getBean("explicit")).isSameAs(OVERRIDE_SERVICE); - assertThat(context.getBean("implicitName")).isSameAs(OVERRIDE_SERVICE); + String byTypeGeneratedBeanName = "java.lang.Integer#0"; + assertThat(context.getBeanDefinitionNames()).contains("descriptionBean", byTypeGeneratedBeanName); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); + assertThat(context.getBean(byTypeGeneratedBeanName)).isEqualTo(42); } @Test void canOverrideBeanProducedByFactoryBeanWithClassObjectTypeAttribute() { - AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class); - RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); - factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, SomeInterface.class); - context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition); - context.register(OverriddenFactoryBean.class); - + AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(CharSequence.class); context.refresh(); - assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE); + assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden"); } @Test void canOverrideBeanProducedByFactoryBeanWithResolvableTypeObjectTypeAttribute() { - AnnotationConfigApplicationContext context = createContext(OverriddenFactoryBean.class); - RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); - ResolvableType objectType = ResolvableType.forClass(SomeInterface.class); - factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectType); - context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition); - context.register(OverriddenFactoryBean.class); - + AnnotationConfigApplicationContext context = prepareContextWithFactoryBean(ResolvableType.forClass(CharSequence.class)); context.refresh(); - assertThat(context.getBean("beanToBeOverridden")).isSameAs(OVERRIDE); + assertThat(context.getBean("beanToBeOverridden")).isEqualTo("overridden"); + } + + private AnnotationConfigApplicationContext prepareContextWithFactoryBean(Object objectTypeAttribute) { + AnnotationConfigApplicationContext context = createContext(CaseOverrideBeanProducedByFactoryBean.class); + context.registerBean("testFactoryBean", TestFactoryBean.class, TestFactoryBean::new); + RootBeanDefinition factoryBeanDefinition = new RootBeanDefinition(TestFactoryBean.class); + factoryBeanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, objectTypeAttribute); + context.registerBeanDefinition("beanToBeOverridden", factoryBeanDefinition); + return context; } @Test void postProcessorShouldNotTriggerEarlyInitialization() { - AnnotationConfigApplicationContext context = createContext(EagerInitBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByTypeWithReplaceOrCreateStrategy.class); context.register(FactoryBeanRegisteringPostProcessor.class); context.register(EarlyBeanInitializationDetector.class); - context.register(EagerInitBean.class); assertThatNoException().isThrownBy(context::refresh); } @Test void allowReplaceDefinitionWhenSingletonDefinitionPresent() { - AnnotationConfigApplicationContext context = createContext(SingletonBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByName.class); RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); definition.setScope(BeanDefinition.SCOPE_SINGLETON); - context.registerBeanDefinition("singleton", definition); - context.register(SingletonBean.class); + context.registerBeanDefinition("descriptionBean", definition); assertThatNoException().isThrownBy(context::refresh); - assertThat(context.isSingleton("singleton")).as("isSingleton").isTrue(); - assertThat(context.getBean("singleton")).as("overridden").isEqualTo("USED THIS"); + assertThat(context.isSingleton("descriptionBean")).as("isSingleton").isTrue(); + assertThat(context.getBean("descriptionBean")).isEqualTo("overridden"); } @Test void copyDefinitionPrimaryFallbackAndScope() { - AnnotationConfigApplicationContext context = createContext(SingletonBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByName.class); context.getBeanFactory().registerScope("customScope", new SimpleThreadScope()); RootBeanDefinition definition = new RootBeanDefinition(String.class, () -> "ORIGINAL"); definition.setScope("customScope"); definition.setPrimary(true); definition.setFallback(true); - context.registerBeanDefinition("singleton", definition); - context.register(SingletonBean.class); + context.registerBeanDefinition("descriptionBean", definition); assertThatNoException().isThrownBy(context::refresh); - assertThat(context.getBeanDefinition("singleton")) + assertThat(context.getBeanDefinition("descriptionBean")) .isNotSameAs(definition) .matches(BeanDefinition::isPrimary, "isPrimary") .matches(BeanDefinition::isFallback, "isFallback") @@ -183,22 +177,20 @@ class BeanOverrideBeanFactoryPostProcessorTests { @Test void createDefinitionShouldSetQualifierElement() { - AnnotationConfigApplicationContext context = createContext(QualifiedBean.class); - context.registerBeanDefinition("singleton", new RootBeanDefinition(String.class, () -> "ORIGINAL")); - context.register(QualifiedBean.class); + AnnotationConfigApplicationContext context = createContext(CaseByNameWithQualifier.class); + context.registerBeanDefinition("descriptionBean", new RootBeanDefinition(String.class, () -> "ORIGINAL")); assertThatNoException().isThrownBy(context::refresh); - - assertThat(context.getBeanDefinition("singleton")) + assertThat(context.getBeanDefinition("descriptionBean")) .isInstanceOfSatisfying(RootBeanDefinition.class, this::isTheValueField); } private void isTheValueField(RootBeanDefinition def) { assertThat(def.getQualifiedElement()).isInstanceOfSatisfying(Field.class, field -> { - assertThat(field.getDeclaringClass()).isEqualTo(QualifiedBean.class); + assertThat(field.getDeclaringClass()).isEqualTo(CaseByNameWithQualifier.class); assertThat(field.getName()).as("annotated field name") - .isEqualTo("value"); + .isEqualTo("description"); }); } @@ -210,99 +202,67 @@ class BeanOverrideBeanFactoryPostProcessorTests { } - /* - Classes to parse and register with the bean post processor - ----- - Note that some of these are both a @Configuration class and bean override field holder. - This is for this test convenience, as typically the bean override annotated fields - should not be in configuration classes but rather in test case classes - (where a TestExecutionListener automatically discovers and parses them). - */ + static class CaseByName { - static final SomeInterface OVERRIDE = new SomeImplementation(); + @DummyBean(beanName = "descriptionBean") + private String description; - static final ExampleService OVERRIDE_SERVICE = new FailingExampleService(); - - static class ReplaceBeans { - - @ExampleBeanOverrideAnnotation(value = "useThis", beanName = "explicit") - private ExampleService explicitName; - - @ExampleBeanOverrideAnnotation(value = "useThis") - private ExampleService implicitName; - - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - static class CreateIfOriginalIsMissingBean { + static class CaseByType { - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true, beanName = "explicit") - private ExampleService explicitName; + @DummyBean + private Integer counter; - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true) - private ExampleService implicitName; - - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - @Configuration(proxyBeanMethods = false) - static class OverriddenFactoryBean { + static class CaseByNameAndByType { - @ExampleBeanOverrideAnnotation(value = "fOverride", beanName = "beanToBeOverridden") - SomeInterface f; + @DummyBean(beanName = "descriptionBean") + private String description; - static SomeInterface fOverride() { - return OVERRIDE; - } + @DummyBean + private Integer counter; - @Bean - TestFactoryBean testFactoryBean() { - return new TestFactoryBean(); - } } - static class EagerInitBean { + static class CaseByTypeWithReplaceOrCreateStrategy { - @ExampleBeanOverrideAnnotation(value = "useThis", createIfMissing = true) - private ExampleService service; + @DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private String description; - static ExampleService useThis() { - return OVERRIDE_SERVICE; - } } - static class SingletonBean { + static class CaseByNameAndByTypeWithReplaceOrCreateStrategy { - @ExampleBeanOverrideAnnotation(beanName = "singleton", - value = "useThis", createIfMissing = false) - private String value; + @DummyBean(beanName = "descriptionBean", strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private String description; + + @DummyBean(strategy = BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION) + private Integer counter; - static String useThis() { - return "USED THIS"; - } } - static class QualifiedBean { + static class CaseOverrideBeanProducedByFactoryBean { + + @DummyBean(beanName = "beanToBeOverridden") + CharSequence description; + + } + + static class CaseByNameWithQualifier { @Qualifier("preferThis") - @ExampleBeanOverrideAnnotation(beanName = "singleton", - value = "useThis", createIfMissing = false) - private String value; + @DummyBean(beanName = "descriptionBean") + private String description; - static String useThis() { - return "USED THIS"; - } } static class TestFactoryBean implements FactoryBean { @Override public Object getObject() { - return new SomeImplementation(); + return "test"; } @Override @@ -341,10 +301,4 @@ class BeanOverrideBeanFactoryPostProcessorTests { } } - interface SomeInterface { - } - - static class SomeImplementation implements SomeInterface { - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java index d26473270a..bf70345024 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java @@ -25,10 +25,9 @@ import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Green; import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Orange; -import org.springframework.test.context.bean.override.convention.TestBean; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * Tests for {@link BeanOverrideContextCustomizerFactory}. @@ -48,14 +47,14 @@ class BeanOverrideContextCustomizerFactoryTests { void createContextCustomizerWhenTestHasSingleBeanOverride() { BeanOverrideContextCustomizer customizer = createContextCustomizer(Test1.class); assertThat(customizer).isNotNull(); - assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class)); + assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class)); } @Test void createContextCustomizerWhenNestedTestHasSingleBeanOverrideInParent() { BeanOverrideContextCustomizer customizer = createContextCustomizer(Orange.class); assertThat(customizer).isNotNull(); - assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class)); + assertThat(customizer.getMetadata()).singleElement().satisfies(dummyMetadata(null, String.class)); } @Test @@ -63,25 +62,19 @@ class BeanOverrideContextCustomizerFactoryTests { BeanOverrideContextCustomizer customizer = createContextCustomizer(Green.class); assertThat(customizer).isNotNull(); assertThat(customizer.getMetadata()) - .anySatisfy(testMetadata(null, String.class)) - .anySatisfy(testMetadata("counterBean", Integer.class)) + .anySatisfy(dummyMetadata(null, String.class)) + .anySatisfy(dummyMetadata("counterBean", Integer.class)) .hasSize(2); } - @Test - void createContextCustomizerWhenTestHasInvalidTestBeanTargetMethod() { - assertThatIllegalStateException() - .isThrownBy(() -> createContextCustomizer(InvalidTestMissingMethod.class)) - .withMessageContaining("Failed to find a static test bean factory method"); + + private Consumer dummyMetadata(@Nullable String beanName, Class beanType) { + return dummyMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION); } - - private Consumer testMetadata(@Nullable String beanName, Class beanType) { - return overrideMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION); - } - - private Consumer overrideMetadata(@Nullable String beanName, Class beanType, BeanOverrideStrategy strategy) { + private Consumer dummyMetadata(@Nullable String beanName, Class beanType, BeanOverrideStrategy strategy) { return metadata -> { + assertThat(metadata).isExactlyInstanceOf(DummyOverrideMetadata.class); assertThat(metadata.getBeanName()).isEqualTo(beanName); assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType); assertThat(metadata.getStrategy()).isEqualTo(strategy); @@ -95,24 +88,16 @@ class BeanOverrideContextCustomizerFactoryTests { static class Test1 { - @TestBean(methodName = "descriptor") + @DummyBean private String descriptor; - private static String descriptor() { - return "Overridden descriptor"; - } - } static class Test2 { - @TestBean(methodName = "name") + @DummyBean private String name; - private static String name() { - return "Overridden name"; - } - @Nested class Orange { @@ -121,24 +106,10 @@ class BeanOverrideContextCustomizerFactoryTests { @Nested class Green { - @TestBean(name = "counterBean", methodName = "counter") + @DummyBean(beanName = "counterBean") private Integer counter; - private static Integer counter() { - return 42; - } } } - static class InvalidTestMissingMethod { - - @TestBean(methodName = "descriptor") - private String descriptor; - - private String descriptor() { - return "Never called, not static"; - } - - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java new file mode 100644 index 0000000000..55f01fd035 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/DummyBean.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.bean.override; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor; +import org.springframework.util.StringUtils; + +/** + * A dummy {@link BeanOverride} implementation that only handles {@link CharSequence} + * and {@link Integer} and replace them with {@code "overridden"} and {@code 42}, + * respectively. + * + * @author Stephane Nicoll + */ +@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@BeanOverride(DummyBeanOverrideProcessor.class) +@interface DummyBean { + + String beanName() default ""; + + BeanOverrideStrategy strategy() default BeanOverrideStrategy.REPLACE_DEFINITION; + + class DummyBeanOverrideProcessor implements BeanOverrideProcessor { + + @Override + public OverrideMetadata createMetadata(Annotation annotation, Class testClass, Field field) { + DummyBean dummyBean = (DummyBean) annotation; + String beanName = (StringUtils.hasText(dummyBean.beanName()) ? dummyBean.beanName() : null); + return new DummyBeanOverrideProcessor.DummyOverrideMetadata(field, field.getType(), beanName, + dummyBean.strategy()); + } + + // Bare bone, "dummy", implementation that should not override anything + // else than createOverride. + static class DummyOverrideMetadata extends OverrideMetadata { + + DummyOverrideMetadata(Field field, Class typeToOverride, @Nullable String beanName, + BeanOverrideStrategy strategy) { + + super(field, ResolvableType.forClass(typeToOverride), beanName, strategy); + } + + @Override + protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, + @Nullable Object existingBeanInstance) { + + Class beanType = getField().getType(); + if (CharSequence.class.isAssignableFrom(beanType)) { + return "overridden"; + } + else if (Integer.class.isAssignableFrom(beanType)) { + return 42; + } + throw new IllegalStateException("Could not handle bean type " + beanType); + } + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java index 8724b39313..1cf31c70c3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java @@ -16,8 +16,6 @@ package org.springframework.test.context.bean.override; -import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -30,9 +28,8 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; +import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyOverrideMetadata; import org.springframework.test.context.bean.override.example.CustomQualifier; import org.springframework.test.context.bean.override.example.ExampleService; import org.springframework.util.ReflectionUtils; @@ -157,7 +154,7 @@ public class OverrideMetadataTests { } private OverrideMetadata createMetadata(Field field, @Nullable String name) { - return new DummyOverrideMetadata(field, field.getType(), name); + return new DummyOverrideMetadata(field, field.getType(), name, BeanOverrideStrategy.REPLACE_DEFINITION); } private Field field(Class target, String fieldName) { @@ -248,42 +245,4 @@ public class OverrideMetadataTests { @DummyBean public @interface MetaDummyBean {} - @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @BeanOverride(DummyBeanOverrideProcessor.class) - public @interface DummyBean {} - - static class DummyBeanOverrideProcessor implements BeanOverrideProcessor { - - @Override - public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { - return new DummyOverrideMetadata(field, field.getType(), null); - } - } - - static class DummyOverrideMetadata extends OverrideMetadata { - - @Nullable - private final String beanName; - - DummyOverrideMetadata(Field field, Class typeToOverride, @Nullable String beanName) { - super(field, ResolvableType.forClass(typeToOverride), beanName, BeanOverrideStrategy.REPLACE_DEFINITION); - this.beanName = beanName; - } - - @Override - @Nullable - public String getBeanName() { - return this.beanName; - } - - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, - @Nullable Object existingBeanInstance) { - - return BeanOverrideStrategy.REPLACE_DEFINITION; - } - } - } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java deleted file mode 100644 index 81e61b8870..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideAnnotation.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.test.context.bean.override.BeanOverride; - -@BeanOverride(ExampleBeanOverrideProcessor.class) -@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExampleBeanOverrideAnnotation { - - String DEFAULT_VALUE = "TEST OVERRIDE"; - // Any metadata using this as the prefix for the bean name will be considered equal - String DUPLICATE_TRIGGER = "DUPLICATE"; - - String value() default DEFAULT_VALUE; - - boolean createIfMissing() default false; - - String beanName() default ""; -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java deleted file mode 100644 index 6ecbe2f1bc..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/ExampleBeanOverrideProcessor.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; - -import org.springframework.core.ResolvableType; -import org.springframework.test.context.bean.override.BeanOverrideProcessor; -import org.springframework.test.context.bean.override.OverrideMetadata; - -// Intentionally NOT public -class ExampleBeanOverrideProcessor implements BeanOverrideProcessor { - - @Override - public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { - if (!(overrideAnnotation instanceof ExampleBeanOverrideAnnotation annotation)) { - throw new IllegalStateException("unexpected annotation"); - } - if (annotation.value().startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER)) { - return new TestOverrideMetadata(annotation.value()); - } - return new TestOverrideMetadata(field, annotation, ResolvableType.forField(field, testClass)); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java deleted file mode 100644 index 49410ca315..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestBeanOverrideMetaAnnotation.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2002-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@ExampleBeanOverrideAnnotation("foo") -public @interface TestBeanOverrideMetaAnnotation { } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java deleted file mode 100644 index 5395937a5d..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/example/TestOverrideMetadata.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2002-2024 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.test.context.bean.override.example; - - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.core.ResolvableType; -import org.springframework.lang.Nullable; -import org.springframework.test.context.bean.override.BeanOverrideStrategy; -import org.springframework.test.context.bean.override.OverrideMetadata; -import org.springframework.util.StringUtils; - -import static org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation.DEFAULT_VALUE; - -class TestOverrideMetadata extends OverrideMetadata { - - @Nullable - private final Method method; - - private final String methodName; - - @Nullable - private static Method findMethod(AnnotatedElement element, String methodName) { - if (DEFAULT_VALUE.equals(methodName)) { - return null; - } - if (element instanceof Field f) { - for (Method m : f.getDeclaringClass().getDeclaredMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - continue; - } - if (m.getName().equals(methodName)) { - return m; - } - } - throw new IllegalStateException("Expected a static method named <" + methodName + "> alongside annotated field <" + f.getName() + ">"); - } - if (element instanceof Method m) { - if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) { - return m; - } - throw new IllegalStateException("Expected the annotated method to be static and named <" + methodName + ">"); - } - if (element instanceof Class c) { - for (Method m : c.getDeclaredMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - continue; - } - if (m.getName().equals(methodName)) { - return m; - } - } - throw new IllegalStateException("Expected a static method named <" + methodName + "> on annotated class <" + c.getSimpleName() + ">"); - } - throw new IllegalStateException("Expected the annotated element to be a Field, Method or Class"); - } - - public TestOverrideMetadata(Field field, ExampleBeanOverrideAnnotation overrideAnnotation, ResolvableType typeToOverride) { - super(field, typeToOverride, overrideAnnotation.beanName(), overrideAnnotation.createIfMissing() ? - BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION: BeanOverrideStrategy.REPLACE_DEFINITION); - this.method = findMethod(field, overrideAnnotation.value()); - this.methodName = overrideAnnotation.value(); - } - - //Used to trigger duplicate detection in parser test - TestOverrideMetadata(String duplicateTrigger) { - super(null, null, duplicateTrigger, null); - this.method = null; - this.methodName = duplicateTrigger; - } - - @Override - public String getBeanName() { - String name = super.getBeanName(); - if (StringUtils.hasText(name)) { - return name; - } - return getField().getName(); - } - - String getAnnotationMethodName() { - return this.methodName; - } - - @Override - protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) { - if (this.method == null) { - return DEFAULT_VALUE; - } - try { - this.method.setAccessible(true); - return this.method.invoke(null); - } - catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean equals(Object obj) { - if (this.method == null) { - return obj instanceof TestOverrideMetadata tem && - tem.getBeanName() != null && - tem.getBeanName().startsWith(ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER); - } - return super.equals(obj); - } - - @Override - public int hashCode() { - if (this.method == null) { - return ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER.hashCode(); - } - return super.hashCode(); - } - - @Override - public String toString() { - if (this.method == null) { - return "{" + this.getBeanName() + "}"; - } - return this.methodName; - } -}