Polish
This commit is contained in:
@@ -22,15 +22,13 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mark an annotation as eligible for Bean Override parsing.
|
||||
* Mark an annotation as eligible for Bean Override processing.
|
||||
*
|
||||
* <p>This meta-annotation specifies a {@link BeanOverrideProcessor} class which
|
||||
* must be capable of handling the composed annotation that is meta-annotated
|
||||
* with {@code @BeanOverride}.
|
||||
* <p>Specifying this annotation triggers the defined {@link BeanOverrideProcessor}
|
||||
* which must be capable of handling the composed annotation and its attributes.
|
||||
*
|
||||
* <p>The composed annotation that is meta-annotated with {@code @BeanOverride}
|
||||
* must have a {@code RetentionPolicy} of {@link RetentionPolicy#RUNTIME RUNTIME}
|
||||
* and a {@code Target} of {@link ElementType#FIELD FIELD}.
|
||||
* <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}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
@@ -41,8 +39,8 @@ import java.lang.annotation.Target;
|
||||
public @interface BeanOverride {
|
||||
|
||||
/**
|
||||
* A {@link BeanOverrideProcessor} implementation class by which the composed
|
||||
* annotation should be processed.
|
||||
* The {@link BeanOverrideProcessor} implementation to trigger against
|
||||
* the composed annotation.
|
||||
*/
|
||||
Class<? extends BeanOverrideProcessor> value();
|
||||
|
||||
|
||||
@@ -22,31 +22,26 @@ import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.aop.scope.ScopedProxyUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryPostProcessor} implementation that processes test classes
|
||||
* and adapt the {@link BeanDefinitionRegistry} for any {@link BeanOverride} it
|
||||
* may define.
|
||||
* and adapt 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
|
||||
@@ -66,10 +61,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
private static final String INFRASTRUCTURE_BEAN_NAME = BeanOverrideBeanFactoryPostProcessor.class.getName();
|
||||
|
||||
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME =
|
||||
BeanOverrideBeanFactoryPostProcessor.WrapEarlyBeanPostProcessor.class.getName();
|
||||
|
||||
private final BeanOverrideRegistrar overrideRegistrar;
|
||||
|
||||
@@ -94,14 +85,16 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory,
|
||||
"Bean overriding annotations can only be used on a DefaultListableBeanFactory");
|
||||
postProcessWithRegistry((DefaultListableBeanFactory) beanFactory);
|
||||
if (!(beanFactory instanceof BeanDefinitionRegistry registry)) {
|
||||
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
|
||||
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass());
|
||||
}
|
||||
postProcessWithRegistry(beanFactory, registry);
|
||||
}
|
||||
|
||||
private void postProcessWithRegistry(DefaultListableBeanFactory registry) {
|
||||
private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
|
||||
for (OverrideMetadata metadata : this.overrideRegistrar.getOverrideMetadata()) {
|
||||
registerBeanOverride(registry, metadata);
|
||||
registerBeanOverride(beanFactory, registry, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,15 +109,20 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
to.setScope(from.getScope());
|
||||
}
|
||||
|
||||
private void registerBeanOverride(DefaultListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
|
||||
private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
|
||||
OverrideMetadata overrideMetadata) {
|
||||
|
||||
switch (overrideMetadata.getStrategy()) {
|
||||
case REPLACE_DEFINITION -> registerReplaceDefinition(beanFactory, overrideMetadata, true);
|
||||
case REPLACE_OR_CREATE_DEFINITION -> registerReplaceDefinition(beanFactory, overrideMetadata, false);
|
||||
case REPLACE_DEFINITION ->
|
||||
registerReplaceDefinition(beanFactory, registry, overrideMetadata, true);
|
||||
case REPLACE_OR_CREATE_DEFINITION ->
|
||||
registerReplaceDefinition(beanFactory, registry, overrideMetadata, false);
|
||||
case WRAP_BEAN -> registerWrapBean(beanFactory, overrideMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerReplaceDefinition(DefaultListableBeanFactory beanFactory, OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
|
||||
private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
|
||||
OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
|
||||
|
||||
RootBeanDefinition beanDefinition = createBeanDefinition(overrideMetadata);
|
||||
String beanName = overrideMetadata.getBeanName();
|
||||
@@ -133,13 +131,13 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
if (beanFactory.containsBeanDefinition(beanName)) {
|
||||
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
copyBeanDefinitionDetails(existingBeanDefinition, beanDefinition);
|
||||
beanFactory.removeBeanDefinition(beanName);
|
||||
registry.removeBeanDefinition(beanName);
|
||||
}
|
||||
else if (enforceExistingDefinition) {
|
||||
throw new IllegalStateException("Unable to override bean '" + beanName + "'; there is no" +
|
||||
" bean definition to replace with that name");
|
||||
}
|
||||
beanFactory.registerBeanDefinition(beanName, beanDefinition);
|
||||
registry.registerBeanDefinition(beanName, beanDefinition);
|
||||
|
||||
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
|
||||
if (beanFactory.isSingleton(beanName)) {
|
||||
@@ -160,7 +158,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
|
||||
* phase.
|
||||
*/
|
||||
private void registerWrapBean(DefaultListableBeanFactory beanFactory, OverrideMetadata metadata) {
|
||||
private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata) {
|
||||
Set<String> existingBeanNames = getExistingBeanNames(beanFactory, metadata.getBeanType());
|
||||
String beanName = metadata.getBeanName();
|
||||
if (!existingBeanNames.contains(beanName)) {
|
||||
@@ -177,7 +175,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
return definition;
|
||||
}
|
||||
|
||||
private Set<String> getExistingBeanNames(DefaultListableBeanFactory beanFactory, ResolvableType resolvableType) {
|
||||
private Set<String> getExistingBeanNames(ConfigurableListableBeanFactory beanFactory, ResolvableType resolvableType) {
|
||||
Set<String> beans = new LinkedHashSet<>(
|
||||
Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
|
||||
Class<?> type = resolvableType.resolve(Object.class);
|
||||
@@ -193,39 +191,6 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||
return beans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link BeanOverrideBeanFactoryPostProcessor} with a {@link BeanDefinitionRegistry}.
|
||||
* <p>Not required when using the Spring TestContext Framework, as registration
|
||||
* is automatic via the
|
||||
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
|
||||
* mechanism.
|
||||
* @param registry the bean definition registry
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry) {
|
||||
RuntimeBeanReference registrarReference = new RuntimeBeanReference(BeanOverrideRegistrar.INFRASTRUCTURE_BEAN_NAME);
|
||||
// Early processor
|
||||
addInfrastructureBeanDefinition(
|
||||
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
|
||||
constructorArgs.addIndexedArgumentValue(0, registrarReference));
|
||||
|
||||
// Main processor
|
||||
addInfrastructureBeanDefinition(
|
||||
registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
|
||||
constructorArgs.addIndexedArgumentValue(0, registrarReference));
|
||||
}
|
||||
|
||||
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);
|
||||
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
|
||||
constructorArgumentsConsumer.accept(constructorArguments);
|
||||
registry.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
PriorityOrdered {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideBeanFactoryPostProcessor.WrapEarlyBeanPostProcessor;
|
||||
|
||||
/**
|
||||
* {@link ContextCustomizer} implementation that registers the necessary
|
||||
* infrastructure to support {@linkplain BeanOverride bean overriding}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
class BeanOverrideContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private static final String REGISTRAR_BEAN_NAME =
|
||||
"org.springframework.test.context.bean.override.internalBeanOverrideRegistrar";
|
||||
|
||||
private static final String INFRASTRUCTURE_BEAN_NAME =
|
||||
"org.springframework.test.context.bean.override.internalBeanOverridePostProcessor";
|
||||
|
||||
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME =
|
||||
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
|
||||
|
||||
|
||||
private final Set<Class<?>> detectedClasses;
|
||||
|
||||
BeanOverrideContextCustomizer(Set<Class<?>> detectedClasses) {
|
||||
this.detectedClasses = detectedClasses;
|
||||
}
|
||||
|
||||
static void registerInfrastructure(BeanDefinitionRegistry registry, Set<Class<?>> detectedClasses) {
|
||||
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
|
||||
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, detectedClasses));
|
||||
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
|
||||
addInfrastructureBeanDefinition(
|
||||
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
|
||||
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
|
||||
addInfrastructureBeanDefinition(
|
||||
registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
|
||||
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
|
||||
}
|
||||
|
||||
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);
|
||||
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
|
||||
constructorArgumentsConsumer.accept(constructorArguments);
|
||||
registry.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
|
||||
if (context instanceof BeanDefinitionRegistry registry) {
|
||||
registerInfrastructure(registry, this.detectedClasses);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
BeanOverrideContextCustomizer other = (BeanOverrideContextCustomizer) obj;
|
||||
return this.detectedClasses.equals(other.detectedClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.detectedClasses.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,13 +20,10 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
import org.springframework.test.context.ContextCustomizerFactory;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
|
||||
/**
|
||||
@@ -62,38 +59,4 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BeanOverrideContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private final Set<Class<?>> detectedClasses;
|
||||
|
||||
BeanOverrideContextCustomizer(Set<Class<?>> detectedClasses) {
|
||||
this.detectedClasses = detectedClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
|
||||
if (context instanceof BeanDefinitionRegistry registry) {
|
||||
BeanOverrideRegistrar.register(registry, this.detectedClasses);
|
||||
BeanOverrideBeanFactoryPostProcessor.register(registry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
BeanOverrideContextCustomizer other = (BeanOverrideContextCustomizer) obj;
|
||||
return this.detectedClasses.equals(other.detectedClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.detectedClasses.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
|
||||
|
||||
/**
|
||||
* Internal parsing utilities to discover the presence of
|
||||
* {@link BeanOverride @BeanOverride} on fields, and create the relevant
|
||||
* {@link OverrideMetadata} accordingly.
|
||||
* {@link BeanOverride @BeanOverride} and create the relevant
|
||||
* {@link OverrideMetadata} if necessary.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Sam Brannen
|
||||
@@ -58,16 +58,13 @@ abstract class BeanOverrideParsingUtils {
|
||||
boolean present = MergedAnnotations.from(field, DIRECT).isPresent(BeanOverride.class);
|
||||
hasBeanOverride.compareAndSet(false, present);
|
||||
});
|
||||
if (hasBeanOverride.get()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return hasBeanOverride.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the specified classes for the presence of fields annotated with
|
||||
* {@link BeanOverride @BeanOverride}, and create an {@link OverrideMetadata}
|
||||
* for each.
|
||||
* for each identified field.
|
||||
* @param classes the classes to parse
|
||||
*/
|
||||
static Set<OverrideMetadata> parse(Iterable<Class<?>> classes) {
|
||||
@@ -77,7 +74,8 @@ abstract class BeanOverrideParsingUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to {@link #parse(Iterable) parse} a single test class.
|
||||
* Convenience method to parse a single test class.
|
||||
* @see #parse(Iterable)
|
||||
*/
|
||||
static Set<OverrideMetadata> parse(Class<?> clazz) {
|
||||
return parse(List.of(clazz));
|
||||
@@ -85,7 +83,6 @@ 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");
|
||||
|
||||
|
||||
@@ -20,17 +20,19 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Strategy interface for Bean Override processing and creation of
|
||||
* {@link OverrideMetadata}.
|
||||
* Strategy interface for Bean Override processing, providing an
|
||||
* {@link OverrideMetadata} that drives how the target bean is overridden.
|
||||
*
|
||||
* <p>Processors are generally linked to one or more specific concrete
|
||||
* annotations (meta-annotated with {@link BeanOverride @BeanOverride}) and
|
||||
* concrete {@link OverrideMetadata} implementations.
|
||||
* <p>At least one composed annotations meta-annotated with
|
||||
* {@link BeanOverride @BeanOverride}) is a companion of this processor and
|
||||
* may provide additional user settings that drive how the concrete
|
||||
* {@link OverrideMetadata} is configured.
|
||||
*
|
||||
* <p>Implementations are required to have a no-argument constructor and be
|
||||
* stateless.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@@ -38,12 +40,12 @@ public interface BeanOverrideProcessor {
|
||||
|
||||
/**
|
||||
* Create an {@link OverrideMetadata} instance for the given annotated field.
|
||||
* @param overrideAnnotation the field annotation
|
||||
* @param testClass the test class being processed, which can be different
|
||||
* from the {@code field.getDeclaringClass()} in case the field is inherited
|
||||
* from a superclass
|
||||
* @param overrideAnnotation the composed annotation that defines the
|
||||
* {@link BeanOverride @BeanOverride} that triggers this processor
|
||||
* @param testClass the test class being processed
|
||||
* @param field the annotated field
|
||||
* @return a new {@link OverrideMetadata} instance
|
||||
* @return the {@link OverrideMetadata} instance that should handle the
|
||||
* given field
|
||||
*/
|
||||
OverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.test.context.bean.override;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -26,11 +25,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -46,40 +41,40 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
|
||||
static final String INFRASTRUCTURE_BEAN_NAME = BeanOverrideRegistrar.class.getName();
|
||||
|
||||
private final Map<OverrideMetadata, String> beanNameRegistry;
|
||||
|
||||
private final Map<String, OverrideMetadata> earlyOverrideMetadata;
|
||||
|
||||
private final Set<OverrideMetadata> overrideMetadata;
|
||||
|
||||
@Nullable
|
||||
private ConfigurableBeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
* Construct a new registrar and immediately parse the provided classes.
|
||||
* 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, to be parsed immediately.
|
||||
* detected to contain bean overriding annotations
|
||||
*/
|
||||
BeanOverrideRegistrar(Set<Class<?>> classesToParse) {
|
||||
Set<OverrideMetadata> metadata = BeanOverrideParsingUtils.parse(classesToParse);
|
||||
Assert.state(!metadata.isEmpty(), "Expected metadata to be produced by parser");
|
||||
this.overrideMetadata = metadata;
|
||||
this.beanNameRegistry = new HashMap<>();
|
||||
this.earlyOverrideMetadata = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this processor's {@link OverrideMetadata} set.
|
||||
*/
|
||||
Set<OverrideMetadata> getOverrideMetadata() {
|
||||
return this.overrideMetadata;
|
||||
this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory,
|
||||
"Bean OverrideRegistrar can only be used with a ConfigurableBeanFactory");
|
||||
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
|
||||
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
|
||||
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
|
||||
"that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass());
|
||||
}
|
||||
this.beanFactory = cbf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the detected {@link OverrideMetadata} instances.
|
||||
*/
|
||||
Set<OverrideMetadata> getOverrideMetadata() {
|
||||
return this.overrideMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,8 +82,8 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
* records and use the {@link OverrideMetadata} to create an override
|
||||
* instance from the provided bean, if relevant.
|
||||
*/
|
||||
final Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
|
||||
final OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
|
||||
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");
|
||||
@@ -98,7 +93,7 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the provided {@link OverrideMetadata} and associated it with a
|
||||
* Register the provided {@link OverrideMetadata} and associate it with a
|
||||
* {@code beanName}.
|
||||
*/
|
||||
void registerNameForMetadata(OverrideMetadata metadata, String beanName) {
|
||||
@@ -109,43 +104,10 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
* Mark the provided {@link OverrideMetadata} and {@code beanName} as "wrap
|
||||
* early", allowing for later bean override using {@link #wrapIfNecessary(Object, String)}.
|
||||
*/
|
||||
public void markWrapEarly(OverrideMetadata metadata, String beanName) {
|
||||
void markWrapEarly(OverrideMetadata metadata, String beanName) {
|
||||
this.earlyOverrideMetadata.put(beanName, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a bean definition for a {@link BeanOverrideRegistrar} if it does
|
||||
* not yet exist. Additionally, each call adds the provided
|
||||
* {@code detectedTestClasses} to the set that will be used as constructor
|
||||
* argument.
|
||||
* <p>The resulting complete set of test classes will be parsed as soon as
|
||||
* the {@link BeanOverrideRegistrar} is constructed.
|
||||
* @param registry the bean definition registry
|
||||
* @param detectedTestClasses a partial {@link Set} of {@link Class classes}
|
||||
* that are expected to contain bean overriding annotations
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry, @Nullable Set<Class<?>> detectedTestClasses) {
|
||||
BeanDefinition definition;
|
||||
if (!registry.containsBeanDefinition(BeanOverrideRegistrar.INFRASTRUCTURE_BEAN_NAME)) {
|
||||
definition = new RootBeanDefinition(BeanOverrideRegistrar.class);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
|
||||
constructorArguments.addIndexedArgumentValue(0, new LinkedHashSet<Class<?>>());
|
||||
registry.registerBeanDefinition(INFRASTRUCTURE_BEAN_NAME, definition);
|
||||
}
|
||||
else {
|
||||
definition = registry.getBeanDefinition(BeanOverrideRegistrar.INFRASTRUCTURE_BEAN_NAME);
|
||||
}
|
||||
|
||||
ConstructorArgumentValues.ValueHolder constructorArg =
|
||||
definition.getConstructorArgumentValues().getIndexedArgumentValue(0, Set.class);
|
||||
@SuppressWarnings({"unchecked", "NullAway"})
|
||||
Set<Class<?>> existing = (Set<Class<?>>) constructorArg.getValue();
|
||||
if (detectedTestClasses != null && existing != null) {
|
||||
existing.addAll(detectedTestClasses);
|
||||
}
|
||||
}
|
||||
|
||||
void inject(Object target, OverrideMetadata overrideMetadata) {
|
||||
String beanName = this.beanNameRegistry.get(overrideMetadata);
|
||||
Assert.state(StringUtils.hasLength(beanName),
|
||||
@@ -170,4 +132,5 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
|
||||
throw new BeanCreationException("Could not inject field '" + field + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,30 +17,34 @@
|
||||
package org.springframework.test.context.bean.override;
|
||||
|
||||
/**
|
||||
* Strategies for bean override instantiation.
|
||||
* Strategies for bean override processing.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
public enum BeanOverrideStrategy {
|
||||
|
||||
/**
|
||||
* Replace a given bean definition, immediately preparing a singleton instance.
|
||||
* <p>Requires that the original bean definition exists.
|
||||
* <p>Fails if the original bean definition exists. To create a new bean
|
||||
* definition in such a case, use {@link #REPLACE_OR_CREATE_DEFINITION}.
|
||||
*/
|
||||
REPLACE_DEFINITION,
|
||||
|
||||
/**
|
||||
* Replace a given bean definition, immediately preparing a singleton instance.
|
||||
* <p>If the original bean definition does not exist, an override definition
|
||||
* will be created instead of failing.
|
||||
* Replace or create a given bean definition, immediately preparing a
|
||||
* singleton instance.
|
||||
* <p>Contrary to {@link #REPLACE_DEFINITION} this create a new bean
|
||||
* definition if the target bean definition does not exist rather than
|
||||
* failing.
|
||||
*/
|
||||
REPLACE_OR_CREATE_DEFINITION,
|
||||
|
||||
/**
|
||||
* Intercept and process an early bean reference rather than a bean
|
||||
* definition, allowing variants of bean overriding to wrap the instance
|
||||
* (e.g. to delegate to actual methods in the context of a mocking "spy").
|
||||
* definition, allowing variants of bean overriding to wrap the instance.
|
||||
* For instance, to delegate to actual methods in the context of a mocking "spy".
|
||||
*/
|
||||
WRAP_BEAN
|
||||
|
||||
|
||||
@@ -20,17 +20,15 @@ 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;
|
||||
|
||||
/**
|
||||
* {@code TestExecutionListener} that enables Bean Override support in tests,
|
||||
* injecting overridden beans in appropriate fields of the test instance.
|
||||
*
|
||||
* <p>Some Bean Override implementations might additionally require the use of
|
||||
* additional listeners, which should be mentioned in the javadoc for the
|
||||
* corresponding annotations.
|
||||
* {@link TestExecutionListener} implementation that enables Bean Override
|
||||
* support in tests, injecting overridden beans in appropriate fields of the
|
||||
* test instance.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
@@ -56,9 +54,9 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
}
|
||||
|
||||
/**
|
||||
* Using a registered {@link BeanOverrideBeanFactoryPostProcessor}, find metadata
|
||||
* associated with the current test class and ensure fields are injected
|
||||
* with the overridden bean instance.
|
||||
* 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.
|
||||
*/
|
||||
protected void injectFields(TestContext testContext) {
|
||||
postProcessFields(testContext, (testMetadata, overrideRegistrar) -> overrideRegistrar.inject(
|
||||
@@ -66,9 +64,9 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
}
|
||||
|
||||
/**
|
||||
* Using a registered {@link BeanOverrideBeanFactoryPostProcessor}, find metadata
|
||||
* associated with the current test class and ensure fields are nulled out
|
||||
* and then re-injected with the overridden bean instance.
|
||||
* 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.
|
||||
* <p>This method does nothing if the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* attribute is not present in the {@code TestContext}.
|
||||
|
||||
@@ -69,7 +69,7 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
* @param methodNames a set of supported names for the factory method
|
||||
* @return the corresponding factory method
|
||||
* @throws IllegalStateException if a matching factory method cannot
|
||||
* be found or multiple methods have a match
|
||||
* be found or multiple methods match
|
||||
*/
|
||||
static Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, String... methodNames) {
|
||||
Assert.isTrue(methodNames.length > 0, "At least one candidate method name is required");
|
||||
|
||||
@@ -151,7 +151,8 @@ class MockitoBeanMetadata extends MockitoMetadata {
|
||||
if (this.serializable) {
|
||||
settings.serializable();
|
||||
}
|
||||
return (T) mock(getBeanType().resolve(), settings);
|
||||
Class<?> targetType = getBeanType().resolve();
|
||||
return (T) mock(targetType, settings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
|
||||
|
||||
/**
|
||||
* A {@link BeanOverrideProcessor} for mockito-related annotations
|
||||
* ({@link MockitoBean} and {@link MockitoSpyBean}).
|
||||
* {@link BeanOverrideProcessor} implementation for Mockito support. Both mocking
|
||||
* and spying are supported.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see MockitoBean
|
||||
* @see MockitoSpyBean
|
||||
*/
|
||||
class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base class for {@link MockitoBeanMetadata} and {@link MockitoSpyBeanMetadata}.
|
||||
* Base class for Mockito override metadata.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
@@ -94,7 +94,7 @@ abstract class MockitoMetadata extends OverrideMetadata {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
|
||||
import org.springframework.test.context.bean.override.OverrideMetadata;
|
||||
import org.springframework.test.util.AopTestUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -39,9 +40,10 @@ import org.springframework.util.StringUtils;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* A complete definition that can be used to create a Mockito spy.
|
||||
* {@link OverrideMetadata} for Mockito {@code spy} support.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
*/
|
||||
class MockitoSpyBeanMetadata extends MockitoMetadata {
|
||||
@@ -52,7 +54,6 @@ class MockitoSpyBeanMetadata extends MockitoMetadata {
|
||||
}
|
||||
|
||||
MockitoSpyBeanMetadata(String name, MockReset reset, boolean proxyTargetAware, Field field, ResolvableType typeToSpy) {
|
||||
|
||||
super(name, reset, proxyTargetAware, field, typeToSpy, BeanOverrideStrategy.WRAP_BEAN);
|
||||
Assert.notNull(typeToSpy, "typeToSpy must not be null");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user