Polish "Bean Overriding in Tests" support
This commit is contained in:
@@ -22,25 +22,23 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mark an annotation as eligible for Bean Override processing.
|
||||
* Mark a composed annotation as eligible for Bean Override processing.
|
||||
*
|
||||
* <p>Specifying this annotation triggers the defined {@link BeanOverrideProcessor}
|
||||
* <p>Specifying this annotation triggers the configured {@link BeanOverrideProcessor}
|
||||
* which must be capable of handling the composed annotation and its attributes.
|
||||
*
|
||||
* <p>The composed annotation is meant to be detected on fields only so it is
|
||||
* expected that it has a {@code Target} of {@link ElementType#FIELD FIELD}.
|
||||
* <p>Since the composed annotation should only be applied to fields, it is
|
||||
* expected that it has a {@link Target} of {@link ElementType#FIELD FIELD}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see BeanOverrideBeanFactoryPostProcessor
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface BeanOverride {
|
||||
|
||||
/**
|
||||
* The {@link BeanOverrideProcessor} implementation to trigger against
|
||||
* the composed annotation.
|
||||
* The {@link BeanOverrideProcessor} implementation to use.
|
||||
*/
|
||||
Class<? extends BeanOverrideProcessor> value();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryPostProcessor} implementation that processes test classes
|
||||
* and adapt the {@link BeanFactory} for any {@link BeanOverride} it may define.
|
||||
* and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define.
|
||||
*
|
||||
* <p>A set of classes from which to parse {@link OverrideMetadata} must be
|
||||
* provided to this processor. Each test class is expected to use any
|
||||
@@ -51,7 +51,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* <p>The provided classes are fully parsed at creation to build a metadata set.
|
||||
* This processor implements several {@link BeanOverrideStrategy overriding
|
||||
* strategy} and chooses the correct one according to each override metadata
|
||||
* strategies} and chooses the correct one according to each override metadata's
|
||||
* {@link OverrideMetadata#getStrategy()} method. Additionally, it provides
|
||||
* support for injecting the overridden bean instances into their corresponding
|
||||
* annotated {@link Field fields}.
|
||||
@@ -61,7 +61,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
|
||||
private final BeanOverrideRegistrar overrideRegistrar;
|
||||
|
||||
|
||||
@@ -195,14 +194,13 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
PriorityOrdered {
|
||||
|
||||
private final BeanOverrideRegistrar overrideRegistrar;
|
||||
private final Map<String, Object> earlyReferences = new ConcurrentHashMap<>(16);
|
||||
|
||||
private final Map<String, Object> earlyReferences;
|
||||
private final BeanOverrideRegistrar overrideRegistrar;
|
||||
|
||||
|
||||
private WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) {
|
||||
this.overrideRegistrar = registrar;
|
||||
this.earlyReferences = new ConcurrentHashMap<>(16);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
|
||||
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
|
||||
|
||||
if (!registry.containsBeanDefinition(beanName)) {
|
||||
RootBeanDefinition definition = new RootBeanDefinition(clazz);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
@@ -84,12 +84,11 @@ abstract class BeanOverrideParsingUtils {
|
||||
private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> metadataSet) {
|
||||
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
|
||||
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
|
||||
Assert.state(mergedAnnotation.isMetaPresent(), "@BeanOverride annotation must be meta-present");
|
||||
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
|
||||
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
|
||||
|
||||
BeanOverride beanOverride = mergedAnnotation.synthesize();
|
||||
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
|
||||
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
|
||||
Assert.state(metaSource != null, "Meta-annotation source must not be null");
|
||||
Annotation composedAnnotation = metaSource.synthesize();
|
||||
|
||||
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
|
||||
|
||||
@@ -20,11 +20,11 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Strategy interface for Bean Override processing, providing an
|
||||
* Strategy interface for Bean Override processing, providing
|
||||
* {@link OverrideMetadata} that drives how the target bean is overridden.
|
||||
*
|
||||
* <p>At least one composed annotations meta-annotated with
|
||||
* {@link BeanOverride @BeanOverride}) is a companion of this processor and
|
||||
* <p>At least one composed annotation that is meta-annotated with
|
||||
* {@link BeanOverride @BeanOverride} must be a companion of this processor and
|
||||
* may provide additional user settings that drive how the concrete
|
||||
* {@link OverrideMetadata} is configured.
|
||||
*
|
||||
|
||||
@@ -41,23 +41,22 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
|
||||
private final Map<OverrideMetadata, String> beanNameRegistry;
|
||||
private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
|
||||
|
||||
private final Map<String, OverrideMetadata> earlyOverrideMetadata;
|
||||
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
|
||||
|
||||
private final Set<OverrideMetadata> overrideMetadata;
|
||||
|
||||
@Nullable
|
||||
private ConfigurableBeanFactory beanFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new registrar and immediately parse the provided classes.
|
||||
* @param classesToParse the initial set of classes that have been
|
||||
* detected to contain bean overriding annotations
|
||||
*/
|
||||
BeanOverrideRegistrar(Set<Class<?>> classesToParse) {
|
||||
this.beanNameRegistry = new HashMap<>();
|
||||
this.earlyOverrideMetadata = new HashMap<>();
|
||||
this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse);
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the detected {@link OverrideMetadata} instances.
|
||||
* Get the detected {@link OverrideMetadata} instances.
|
||||
*/
|
||||
Set<OverrideMetadata> getOverrideMetadata() {
|
||||
return this.overrideMetadata;
|
||||
@@ -85,16 +84,16 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
|
||||
OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
|
||||
if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP_BEAN) {
|
||||
bean = metadata.createOverride(beanName, null, bean);
|
||||
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
|
||||
bean = metadata.createOverride(beanName, null, bean);
|
||||
metadata.track(bean, this.beanFactory);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the provided {@link OverrideMetadata} and associate it with a
|
||||
* {@code beanName}.
|
||||
* Register the provided {@link OverrideMetadata} and associate it with the
|
||||
* supplied {@code beanName}.
|
||||
*/
|
||||
void registerNameForMetadata(OverrideMetadata metadata, String beanName) {
|
||||
this.beanNameRegistry.put(metadata, beanName);
|
||||
@@ -119,7 +118,7 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Object existingValue = ReflectionUtils.getField(field, target);
|
||||
Assert.state(this.beanFactory != null, "beanFactory must not be null");
|
||||
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
|
||||
Object bean = this.beanFactory.getBean(beanName, field.getType());
|
||||
if (existingValue == bean) {
|
||||
return;
|
||||
|
||||
@@ -35,7 +35,7 @@ public enum BeanOverrideStrategy {
|
||||
/**
|
||||
* Replace or create a given bean definition, immediately preparing a
|
||||
* singleton instance.
|
||||
* <p>Contrary to {@link #REPLACE_DEFINITION} this create a new bean
|
||||
* <p>Contrary to {@link #REPLACE_DEFINITION}, this creates a new bean
|
||||
* definition if the target bean definition does not exist rather than
|
||||
* failing.
|
||||
*/
|
||||
@@ -43,8 +43,9 @@ public enum BeanOverrideStrategy {
|
||||
|
||||
/**
|
||||
* Intercept and process an early bean reference rather than a bean
|
||||
* definition, allowing variants of bean overriding to wrap the instance.
|
||||
* For instance, to delegate to actual methods in the context of a mocking "spy".
|
||||
* definition, allowing variants of bean overriding to wrap the instance
|
||||
* — for example, to delegate to actual methods in the context of a
|
||||
* mocking "spy".
|
||||
*/
|
||||
WRAP_BEAN
|
||||
|
||||
|
||||
@@ -20,15 +20,13 @@ import java.lang.reflect.Field;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link TestExecutionListener} implementation that enables Bean Override
|
||||
* support in tests, injecting overridden beans in appropriate fields of the
|
||||
* test instance.
|
||||
* {@code TestExecutionListener} that enables Bean Override support in tests,
|
||||
* injecting overridden beans in appropriate fields of the test instance.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
@@ -54,9 +52,9 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the test instance and make sure that flagged fields for bean
|
||||
* overriding are processed. Each field get is value updated with the
|
||||
* overridden bean instance.
|
||||
* Process the test instance and make sure that fields flagged for bean
|
||||
* overriding are processed.
|
||||
* <p>Each field's value will be updated with the overridden bean instance.
|
||||
*/
|
||||
protected void injectFields(TestContext testContext) {
|
||||
postProcessFields(testContext, (testMetadata, overrideRegistrar) -> overrideRegistrar.inject(
|
||||
@@ -64,9 +62,10 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the test instance and make sure that flagged fields for bean
|
||||
* overriding are processed. If a fresh instance is required, the field
|
||||
* is nulled out and then re-injected with the overridden bean instance.
|
||||
* Process the test instance and make sure that fields flagged for bean
|
||||
* overriding are processed.
|
||||
* <p>If a fresh instance is required, the field is nulled out and then
|
||||
* re-injected with the overridden bean instance.
|
||||
* <p>This method does nothing if the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* attribute is not present in the {@code TestContext}.
|
||||
|
||||
@@ -26,15 +26,15 @@ import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Metadata for Bean Override injection points, also responsible for the
|
||||
* creation of the overriding instance.
|
||||
* Metadata for Bean Override injection points, also responsible for creation of
|
||||
* the overriding instance.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: implementations are used as a cache key and
|
||||
* must implement proper {@code equals} and {@code hashCode}t methods.
|
||||
* must implement proper {@code equals()} and {@code hashCode()} methods.
|
||||
*
|
||||
* <p>Specific implementations of metadata can have state to be used during
|
||||
* override {@linkplain #createOverride(String, BeanDefinition, Object)
|
||||
* instance creation} — for example, from further parsing of the
|
||||
* instance creation} — for example, based on further parsing of the
|
||||
* annotation or the annotated field.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
@@ -58,31 +58,32 @@ public abstract class OverrideMetadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean name to override.
|
||||
* Get the bean name to override.
|
||||
* <p>Defaults to the name of the {@link #getField() field}.
|
||||
*/
|
||||
protected String getBeanName() {
|
||||
return this.field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean {@link ResolvableType type} to override.
|
||||
* Get the bean {@linkplain ResolvableType type} to override.
|
||||
*/
|
||||
public ResolvableType getBeanType() {
|
||||
public final ResolvableType getBeanType() {
|
||||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the annotated {@link Field}.
|
||||
* Get the annotated {@link Field}.
|
||||
*/
|
||||
public Field getField() {
|
||||
public final Field getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link BeanOverrideStrategy} for this instance, as a hint on
|
||||
* Get the {@link BeanOverrideStrategy} for this instance, as a hint on
|
||||
* how and when the override instance should be created.
|
||||
*/
|
||||
public BeanOverrideStrategy getStrategy() {
|
||||
public final BeanOverrideStrategy getStrategy() {
|
||||
return this.strategy;
|
||||
}
|
||||
|
||||
@@ -91,9 +92,9 @@ public abstract class OverrideMetadata {
|
||||
* optionally provided with an existing {@link BeanDefinition} and/or an
|
||||
* 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
|
||||
* @param existingBeanInstance an existing instance for that bean name,
|
||||
* @param existingBeanDefinition an existing bean definition for the supplied
|
||||
* bean name, or {@code null} if not relevant
|
||||
* @param existingBeanInstance an existing instance for the supplied bean name
|
||||
* for wrapping purposes, or {@code null} if irrelevant
|
||||
* @return the instance with which to override the bean
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user