From 8172d7adfe23521e55da091124b31b35ccbbe06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Sat, 9 Mar 2024 17:27:44 +0100 Subject: [PATCH] Polish --- .../bean/override/BeanOverrideParser.java | 4 +- .../bean/override/BeanOverrideProcessor.java | 7 +- .../bean/override/BeanOverrideStrategy.java | 9 +- .../BeanOverrideTestExecutionListener.java | 4 +- .../test/bean/override/OverrideMetadata.java | 33 ++++---- .../bean/override/convention/TestBean.java | 84 +++++++++++++------ .../convention/TestBeanOverrideProcessor.java | 20 ++--- 7 files changed, 99 insertions(+), 62 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideParser.java b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideParser.java index 5a2d4acb3ac..c4d25b7c596 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideParser.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideParser.java @@ -100,12 +100,12 @@ class BeanOverrideParser { MergedAnnotations.from(field, MergedAnnotations.SearchStrategy.DIRECT) .stream(BeanOverride.class) .map(bo -> { - var a = bo.getMetaSource(); + MergedAnnotation a = bo.getMetaSource(); Assert.notNull(a, "BeanOverride annotation must be meta-present"); return new AnnotationPair(a.synthesize(), bo); }) .forEach(pair -> { - var metaAnnotation = pair.metaAnnotation().synthesize(); + BeanOverride metaAnnotation = pair.metaAnnotation().synthesize(); final BeanOverrideProcessor processor = getProcessorInstance(metaAnnotation.value()); if (processor == null) { return; diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideProcessor.java index c4621737502..3019754c301 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideProcessor.java @@ -26,7 +26,8 @@ import org.springframework.core.annotation.MergedAnnotation; /** * An interface for Bean Overriding concrete processing. - * Processors are generally linked to one or more specific concrete annotations + * + *

Processors are generally linked to one or more specific concrete annotations * (meta-annotated with {@link BeanOverride}) and specify different steps in the * process of parsing these annotations, ultimately creating * {@link OverrideMetadata} which will be used to instantiate the overrides. @@ -57,8 +58,8 @@ public interface BeanOverrideProcessor { * {@link #getOrDeduceType(Field, Annotation, Class) type}. * Specific implementations of metadata can have state to be used during * override {@link OverrideMetadata#createOverride(String, BeanDefinition, - * Object) instance creation} (e.g. from further parsing the annotation or - * the annotated field). + * Object) instance creation}, that is from further parsing the annotation or + * the annotated field. * @param field the annotated field * @param overrideAnnotation the field annotation * @param typeToOverride the target type diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideStrategy.java b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideStrategy.java index 32bf431495e..bb030b2a583 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideStrategy.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideStrategy.java @@ -26,20 +26,23 @@ package org.springframework.test.bean.override; public enum BeanOverrideStrategy { /** - * Replace a given bean's definition, immediately preparing a singleton + * Replace a given bean definition, immediately preparing a singleton * instance. Enforces the original bean definition to exist. */ REPLACE_DEFINITION, + /** - * Replace a given bean's definition, immediately preparing a singleton + * Replace a given bean definition, immediately preparing a singleton * instance. If the original bean definition does not exist, create the * override definition instead of failing. */ REPLACE_OR_CREATE_DEFINITION, + /** * Intercept and wrap the actual bean instance upon creation, during * {@link org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String) * early bean definition}. */ - WRAP_EARLY_BEAN; + WRAP_EARLY_BEAN + } diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideTestExecutionListener.java index dc86a686cad..a2b5a7f1d5d 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/BeanOverrideTestExecutionListener.java @@ -26,8 +26,8 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution import org.springframework.util.ReflectionUtils; /** - * A {@link TestExecutionListener} that enables Bean Override support in - * tests, injecting overridden beans in appropriate fields. + * A {@link TestExecutionListener} implementation that enables Bean Override + * support in tests, injecting overridden beans in appropriate fields. * *

Some flavors of Bean Override might additionally require the use of * additional listeners, which should be mentioned in the annotation(s) javadoc. diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/OverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/bean/override/OverrideMetadata.java index 4261441bef6..d76f550adf4 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/OverrideMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/OverrideMetadata.java @@ -34,11 +34,14 @@ import org.springframework.lang.Nullable; public abstract class OverrideMetadata { private final Field field; + private final Annotation overrideAnnotation; + private final ResolvableType typeToOverride; + private final BeanOverrideStrategy strategy; - public OverrideMetadata(Field field, Annotation overrideAnnotation, + protected OverrideMetadata(Field field, Annotation overrideAnnotation, ResolvableType typeToOverride, BeanOverrideStrategy strategy) { this.field = field; this.overrideAnnotation = overrideAnnotation; @@ -47,50 +50,46 @@ public abstract class OverrideMetadata { } /** - * Define a short human-readable description of the kind of override this - * OverrideMetadata is about. This is especially useful for - * {@link BeanOverrideProcessor} that produce several subtypes of metadata - * (e.g. "mock" vs "spy"). + * Return a short human-readable description of the kind of override this + * instance handles. */ public abstract String getBeanOverrideDescription(); /** - * Provide the expected bean name to override. Typically, this is either + * Return the expected bean name to override. Typically, this is either * explicitly set in the concrete annotations or defined by the annotated * field's name. - * @return the expected bean name, not null + * @return the expected bean name */ protected String getExpectedBeanName() { return this.field.getName(); } /** - * The field annotated with a {@link BeanOverride}-compatible annotation. - * @return the annotated field + * Return the annotated {@link Field}. */ public Field field() { return this.field; } /** - * The concrete override annotation, i.e. the one meta-annotated with - * {@link BeanOverride}. + * Return the concrete override annotation, that is the one meta-annotated + * with {@link BeanOverride}. */ public Annotation overrideAnnotation() { return this.overrideAnnotation; } /** - * The type to override, as a {@link ResolvableType}. + * Return the bean {@link ResolvableType type} to override. */ public ResolvableType typeToOverride() { return this.typeToOverride; } /** - * Define the broad {@link BeanOverrideStrategy} for this - * {@link OverrideMetadata}, as a hint on how and when the override instance - * should be created. + * Return the {@link BeanOverrideStrategy} for this instance, as a hint on + * how and when the override instance should be created. */ public final BeanOverrideStrategy getBeanOverrideStrategy() { return this.strategy; @@ -99,7 +98,7 @@ public abstract class OverrideMetadata { /** * Create an override instance from this {@link OverrideMetadata}, * optionally provided with an existing {@link BeanDefinition} and/or an - * original instance (i.e. a singleton or an early wrapped instance). + * original instance, that is a singleton or an early wrapped instance. * @param beanName the name of the bean being overridden * @param existingBeanDefinition an existing bean definition for that bean * name, or {@code null} if not relevant @@ -129,7 +128,7 @@ public abstract class OverrideMetadata { if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { return false; } - var that = (OverrideMetadata) obj; + OverrideMetadata that = (OverrideMetadata) obj; return Objects.equals(this.field, that.field) && Objects.equals(this.overrideAnnotation, that.overrideAnnotation) && Objects.equals(this.strategy, that.strategy) && diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBean.java b/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBean.java index d5d74214138..85402eab767 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBean.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBean.java @@ -25,29 +25,63 @@ import java.lang.annotation.Target; import org.springframework.test.bean.override.BeanOverride; /** - * Mark a field to represent a "method" bean override of the bean of the same - * name and inject the field with the overriding instance. + * Mark a field to override a bean instance in the {@code BeanFactory}. * - *

The instance is created from a static method in the declaring class which - * return type is compatible with the annotated field and which name follows the - * convention: + *

The instance is created from a no-arg static method in the declaring + * class whose return type is compatible with the annotated field. The method + * is deduced as follows: *

* - *

The annotated field's name is interpreted to be the name of the original - * bean to override, unless the annotation's {@link #name()} is specified. + *

Consider the following example: + * + *


+ * class CustomerServiceTests {
+ *
+ *     @TestBean
+ *     private CustomerRepository repository;
+ *
+ *     // Tests
+ *
+ *     private static CustomerRepository repositoryTestOverride() {
+ *         return new TestCustomerRepository();
+ *     }
+ * }
+ * + *

In the example above, the {@code repository} bean is replaced by the + * instance generated by the {@code repositoryTestOverride} method. Not only + * the overridden instance is injected in the {@code repository} field, but it + * is also replaced in the {@code BeanFactory} so that other injection points + * for that bean use the override. + * + *

To make things more explicit, the method name can be set, as shown in the + * following example: + * + *


+ * class CustomerServiceTests {
+ *
+ *     @TestBean(methodName = "createTestCustomerRepository")
+ *     private CustomerRepository repository;
+ *
+ *     // Tests
+ *
+ *     private static CustomerRepository createTestCustomerRepository() {
+ *         return new TestCustomerRepository();
+ *     }
+ * }
+ * + *

By default, the name of the bean is inferred from the name of the annotated + * field. To use a different bean name, set the {@link #name()} property. * - * @see TestBeanOverrideProcessor * @author Simon Baslé + * @author Stephane Nicoll * @since 6.2 + * @see TestBeanOverrideProcessor */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @@ -56,23 +90,23 @@ import org.springframework.test.bean.override.BeanOverride; public @interface TestBean { /** - * The method suffix expected as a convention in static methods which - * provides an override instance. + * Required suffix for a method that overrides a bean instance that is + * detected by convention. */ String CONVENTION_SUFFIX = "TestOverride"; /** - * The name of a static method to look for in the Configuration, which will - * be used to instantiate the override bean and inject the annotated field. - *

Default is {@code ""} (the empty String), which is translated into - * the annotated field's name concatenated with the - * {@link #CONVENTION_SUFFIX}. + * Name of a static method to look for in the test, which will be used to + * instantiate the bean to override. + *

Default to {@code ""} (the empty String), which detects the method + * to us by convention. */ String methodName() default ""; /** - * The name of the original bean to override, or {@code ""} (the empty - * String) to deduce the name from the annotated field. + * Name of the bean to override. + *

Default to {@code ""} (the empty String) to use the name of the + * annotated field. */ String name() default ""; } diff --git a/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBeanOverrideProcessor.java index 20eb05fb166..f2b659b0be1 100644 --- a/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/bean/override/convention/TestBeanOverrideProcessor.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.core.ResolvableType; @@ -37,8 +36,8 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Simple {@link BeanOverrideProcessor} primarily made to work with the - * {@link TestBean} annotation but can work with arbitrary override annotations + * {@link BeanOverrideProcessor} implementation primarily made to work with + * {@link TestBean @TestBean}, but can work with arbitrary override annotations * provided the annotated class has a relevant method according to the * convention documented in {@link TestBean}. * @@ -48,19 +47,20 @@ import org.springframework.util.StringUtils; public class TestBeanOverrideProcessor implements BeanOverrideProcessor { /** - * Ensures the {@code enclosingClass} has a static, no-arguments method with - * the provided {@code expectedMethodReturnType} and exactly one of the + * Ensure the given {@code enclosingClass} has a static, no-arguments method + * with the given {@code expectedMethodReturnType} and exactly one of the * {@code expectedMethodNames}. */ public static Method ensureMethod(Class enclosingClass, Class expectedMethodReturnType, String... expectedMethodNames) { + Assert.isTrue(expectedMethodNames.length > 0, "At least one expectedMethodName is required"); Set expectedNames = new LinkedHashSet<>(Arrays.asList(expectedMethodNames)); final List found = Arrays.stream(enclosingClass.getDeclaredMethods()) - .filter(m -> Modifier.isStatic(m.getModifiers())) - .filter(m -> expectedNames.contains(m.getName()) && expectedMethodReturnType - .isAssignableFrom(m.getReturnType())) - .collect(Collectors.toList()); + .filter(method -> Modifier.isStatic(method.getModifiers())) + .filter(method -> expectedNames.contains(method.getName()) + && expectedMethodReturnType.isAssignableFrom(method.getReturnType())) + .toList(); Assert.state(found.size() == 1, () -> "Found " + found.size() + " static methods " + "instead of exactly one, matching a name in " + expectedNames + " with return type " + @@ -87,7 +87,7 @@ public class TestBeanOverrideProcessor implements BeanOverrideProcessor { } // otherwise defer the resolution of the static method until OverrideMetadata#createOverride return new MethodConventionOverrideMetadata(field, null, null, overrideAnnotation, - typeToOverride); + typeToOverride); } static final class MethodConventionOverrideMetadata extends OverrideMetadata {