Rename OverrideMetadata for Bean Overrides

Prior to this commit, OverrideMetadata was the only public type in the
org.springframework.test.context.bean.override package whose name did
not start with BeanOverride. In addition, an OverrideMetadata component
plays multiple roles in addition to serving as a holder for metadata.

This commit therefore renames OverrideMetadata to BeanOverrideHandler.

In addition, this commit updates the affected documentation and renames
the following related methods in the Bean Override support.

- BeanOverrideHandler: createOverride() -> createOverrideInstance()
- BeanOverrideHandler: track() -> trackOverrideInstance()
- BeanOverrideProcessor: createMetadata() -> createHandler()
- BeanOverrideContextCustomizer: getMetadata() -> getBeanOverrideHandlers()
- BeanOverrideRegistrar: registerNameForMetadata() -> registerBeanOverrideHandler()
- BeanOverrideRegistrar: markWrapEarly() -> registerWrappingBeanOverrideHandler()

Closes gh-33702
This commit is contained in:
Sam Brannen
2024-10-16 16:32:38 +02:00
parent ab4fe5a29c
commit c0c78bd67e
28 changed files with 674 additions and 663 deletions

View File

@@ -47,12 +47,12 @@ import org.springframework.util.Assert;
* accordingly.
*
* <p>For each override, the bean factory is prepared according to the chosen
* {@linkplain BeanOverrideStrategy override strategy}. The override value is created,
* if necessary, and the necessary infrastructure is updated to allow the value
* to be injected in the corresponding {@linkplain OverrideMetadata#getField() field}
* of the test class.
* {@linkplain BeanOverrideStrategy override strategy}. The bean override instance
* is created, if necessary, and the related infrastructure is updated to allow
* the bean override instance to be injected into the corresponding
* {@linkplain BeanOverrideHandler#getField() field} of the test class.
*
* <p>This processor does not work against a particular test class, but rather
* <p>This processor does not work against a particular test class but rather
* only prepares the bean factory for the identified, unique set of bean overrides.
*
* @author Simon Baslé
@@ -66,22 +66,23 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
private final Set<OverrideMetadata> metadata;
private final Set<BeanOverrideHandler> beanOverrideHandlers;
private final BeanOverrideRegistrar overrideRegistrar;
private final BeanOverrideRegistrar beanOverrideRegistrar;
/**
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied
* set of {@link OverrideMetadata} to process, using the given
* {@link BeanOverrideRegistrar}.
* @param metadata the {@link OverrideMetadata} instances to process
* @param overrideRegistrar the {@code BeanOverrideRegistrar} used to track
* metadata
* set of {@link BeanOverrideHandler BeanOverrideHandlers} to process, using
* the given {@link BeanOverrideRegistrar}.
* @param beanOverrideHandlers the bean override handlers to process
* @param beanOverrideRegistrar the registrar used to track bean override handlers
*/
BeanOverrideBeanFactoryPostProcessor(Set<OverrideMetadata> metadata, BeanOverrideRegistrar overrideRegistrar) {
this.metadata = metadata;
this.overrideRegistrar = overrideRegistrar;
BeanOverrideBeanFactoryPostProcessor(Set<BeanOverrideHandler> beanOverrideHandlers,
BeanOverrideRegistrar beanOverrideRegistrar) {
this.beanOverrideHandlers = beanOverrideHandlers;
this.beanOverrideRegistrar = beanOverrideRegistrar;
}
@@ -92,27 +93,27 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (OverrideMetadata metadata : this.metadata) {
registerBeanOverride(beanFactory, metadata);
for (BeanOverrideHandler handler : this.beanOverrideHandlers) {
registerBeanOverride(beanFactory, handler);
}
}
private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
String beanName = overrideMetadata.getBeanName();
Field field = overrideMetadata.getField();
private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler) {
String beanName = handler.getBeanName();
Field field = handler.getField();
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName),() -> """
Unable to override bean '%s' for field '%s.%s': a FactoryBean cannot be overridden. \
To override the bean created by the FactoryBean, remove the '&' prefix.""".formatted(
beanName, field.getDeclaringClass().getSimpleName(), field.getName()));
switch (overrideMetadata.getStrategy()) {
case REPLACE -> replaceBean(beanFactory, overrideMetadata, true);
case REPLACE_OR_CREATE -> replaceBean(beanFactory, overrideMetadata, false);
case WRAP -> wrapBean(beanFactory, overrideMetadata);
switch (handler.getStrategy()) {
case REPLACE -> replaceOrCreateBean(beanFactory, handler, true);
case REPLACE_OR_CREATE -> replaceOrCreateBean(beanFactory, handler, false);
case WRAP -> wrapBean(beanFactory, handler);
}
}
private void replaceBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata,
private void replaceOrCreateBean(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
boolean requireExistingBean) {
// NOTE: This method supports 3 distinct scenarios which must be accounted for.
@@ -121,10 +122,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// 2) AOT processing
// 3) AOT runtime
String beanName = overrideMetadata.getBeanName();
String beanName = handler.getBeanName();
BeanDefinition existingBeanDefinition = null;
if (beanName == null) {
beanName = getBeanNameForType(beanFactory, overrideMetadata, requireExistingBean);
beanName = getBeanNameForType(beanFactory, handler, requireExistingBean);
if (beanName != null) {
// We are overriding an existing bean by-type.
beanName = BeanFactoryUtils.transformedBeanName(beanName);
@@ -141,7 +142,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
}
else {
Set<String> candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false);
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
if (candidates.contains(beanName)) {
// We are overriding an existing bean by-name.
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
@@ -150,7 +151,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
throw new IllegalStateException("""
Unable to override bean: there is no bean to replace \
with name [%s] and type [%s]."""
.formatted(beanName, overrideMetadata.getBeanType()));
.formatted(beanName, handler.getBeanType()));
}
}
@@ -180,7 +181,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass().getName());
}
RootBeanDefinition pseudoBeanDefinition = createPseudoBeanDefinition(overrideMetadata);
RootBeanDefinition pseudoBeanDefinition = createPseudoBeanDefinition(handler);
// Generate a name for the nonexistent bean.
if (PSEUDO_BEAN_NAME_PLACEHOLDER.equals(beanName)) {
@@ -190,9 +191,9 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
registry.registerBeanDefinition(beanName, pseudoBeanDefinition);
}
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
overrideMetadata.track(override, beanFactory);
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName);
Object override = handler.createOverrideInstance(beanName, existingBeanDefinition, null);
handler.trackOverrideInstance(override, beanFactory);
this.beanOverrideRegistrar.registerBeanOverrideHandler(handler, beanName);
// Now we have an instance (the override) that we can manually register as a singleton.
//
@@ -202,7 +203,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
//
// As a bonus, by manually registering a singleton during "AOT processing", we allow
// GenericApplicationContext's preDetermineBeanType() method to transparently register
// runtime hints for a proxy generated by the above createOverride() invocation --
// runtime hints for a proxy generated by the above createOverrideInstance() invocation --
// for example, when @MockitoBean creates a mock based on a JDK dynamic proxy.
if (beanFactory.containsSingleton(beanName)) {
destroySingleton(beanFactory, beanName);
@@ -211,73 +212,74 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
/**
* Check that the expected bean name is registered and matches the type to override.
* <p>If so, put the override metadata in the early tracking map.
* Check that a bean with the specified {@link BeanOverrideHandler#getBeanName() name}
* and {@link BeanOverrideHandler#getBeanType() type} is registered.
* <p>If so, put the {@link BeanOverrideHandler} in the early tracking map.
* <p>The map will later be checked to see if a given bean should be wrapped
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference}
* phase.
*/
private void wrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
String beanName = overrideMetadata.getBeanName();
private void wrapBean(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler) {
String beanName = handler.getBeanName();
if (beanName == null) {
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, handler, true);
int candidateCount = candidateNames.size();
if (candidateCount != 1) {
Field field = overrideMetadata.getField();
Field field = handler.getField();
throw new IllegalStateException("""
Unable to select a bean to override by wrapping: found %d bean instances of type %s \
(as required by annotated field '%s.%s')%s"""
.formatted(candidateCount, overrideMetadata.getBeanType(),
.formatted(candidateCount, handler.getBeanType(),
field.getDeclaringClass().getSimpleName(), field.getName(),
(candidateCount > 0 ? ": " + candidateNames : "")));
}
beanName = BeanFactoryUtils.transformedBeanName(candidateNames.iterator().next());
}
else {
Set<String> candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false);
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
if (!candidates.contains(beanName)) {
throw new IllegalStateException("""
Unable to override bean by wrapping: there is no existing bean \
with name [%s] and type [%s]."""
.formatted(beanName, overrideMetadata.getBeanType()));
.formatted(beanName, handler.getBeanType()));
}
}
validateBeanDefinition(beanFactory, beanName);
this.overrideRegistrar.markWrapEarly(overrideMetadata, beanName);
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName);
this.beanOverrideRegistrar.registerWrappingBeanOverrideHandler(handler, beanName);
this.beanOverrideRegistrar.registerBeanOverrideHandler(handler, beanName);
}
@Nullable
private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata,
private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
boolean requireExistingBean) {
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, handler, true);
int candidateCount = candidateNames.size();
if (candidateCount == 1) {
return candidateNames.iterator().next();
}
else if (candidateCount == 0) {
if (requireExistingBean) {
Field field = overrideMetadata.getField();
Field field = handler.getField();
throw new IllegalStateException(
"Unable to override bean: no beans of type %s (as required by annotated field '%s.%s')"
.formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
.formatted(handler.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
}
return null;
}
Field field = overrideMetadata.getField();
Field field = handler.getField();
throw new IllegalStateException("""
Unable to select a bean to override: found %s beans of type %s \
(as required by annotated field '%s.%s'): %s"""
.formatted(candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
.formatted(candidateCount, handler.getBeanType(), field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames));
}
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata,
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
boolean checkAutowiredCandidate) {
ResolvableType resolvableType = metadata.getBeanType();
ResolvableType resolvableType = handler.getBeanType();
Class<?> type = resolvableType.toClass();
// Start with matching bean names for type, excluding FactoryBeans.
@@ -296,7 +298,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// Filter out non-matching autowire candidates.
if (checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(metadata.getField(), true);
DependencyDescriptor descriptor = new DependencyDescriptor(handler.getField(), true);
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
}
// Filter out scoped proxy targets.
@@ -304,7 +306,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// In case of multiple matches, fall back on the field's name as a last resort.
if (beanNames.size() > 1) {
String fieldName = metadata.getField().getName();
String fieldName = handler.getField().getName();
if (beanNames.contains(fieldName)) {
return Set.of(fieldName);
}
@@ -313,21 +315,21 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
/**
* Create a pseudo-{@link BeanDefinition} for the supplied {@link OverrideMetadata},
* Create a pseudo-{@link BeanDefinition} for the supplied {@link BeanOverrideHandler},
* whose {@linkplain RootBeanDefinition#getTargetType() target type} and
* {@linkplain RootBeanDefinition#getQualifiedElement() qualified element} are
* the {@linkplain OverrideMetadata#getBeanType() bean type} and
* the {@linkplain OverrideMetadata#getField() field} of the {@code OverrideMetadata},
* the {@linkplain BeanOverrideHandler#getBeanType() bean type} and
* the {@linkplain BeanOverrideHandler#getField() field} of the {@code BeanOverrideHandler},
* respectively.
* <p>The returned bean definition should <strong>not</strong> be used to create
* a bean instance but rather only for the purpose of having suitable bean
* definition metadata available in the {@link BeanFactory} &mdash; for example,
* for autowiring candidate resolution.
*/
private static RootBeanDefinition createPseudoBeanDefinition(OverrideMetadata metadata) {
RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve());
definition.setTargetType(metadata.getBeanType());
definition.setQualifiedElement(metadata.getField());
private static RootBeanDefinition createPseudoBeanDefinition(BeanOverrideHandler handler) {
RootBeanDefinition definition = new RootBeanDefinition(handler.getBeanType().resolve());
definition.setTargetType(handler.getBeanType());
definition.setQualifiedElement(handler.getField());
return definition;
}

View File

@@ -44,10 +44,10 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
private final Set<OverrideMetadata> metadata;
private final Set<BeanOverrideHandler> handlers;
BeanOverrideContextCustomizer(Set<OverrideMetadata> metadata) {
this.metadata = metadata;
BeanOverrideContextCustomizer(Set<BeanOverrideHandler> handlers) {
this.handlers = handlers;
}
@Override
@@ -58,17 +58,17 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
// to register them as manual singleton instances. In addition, registration of
// the BeanOverrideBeanFactoryPostProcessor as a singleton is a requirement for
// AOT processing, since a bean definition cannot be generated for the
// Set<OverrideMetadata> argument that it accepts in its constructor.
// Set<BeanOverrideHandler> argument that it accepts in its constructor.
BeanOverrideRegistrar beanOverrideRegistrar = new BeanOverrideRegistrar(beanFactory);
beanFactory.registerSingleton(REGISTRAR_BEAN_NAME, beanOverrideRegistrar);
beanFactory.registerSingleton(INFRASTRUCTURE_BEAN_NAME,
new BeanOverrideBeanFactoryPostProcessor(this.metadata, beanOverrideRegistrar));
new BeanOverrideBeanFactoryPostProcessor(this.handlers, beanOverrideRegistrar));
beanFactory.registerSingleton(EARLY_INFRASTRUCTURE_BEAN_NAME,
new WrapEarlyBeanPostProcessor(beanOverrideRegistrar));
}
Set<OverrideMetadata> getMetadata() {
return this.metadata;
Set<BeanOverrideHandler> getBeanOverrideHandlers() {
return this.handlers;
}
@Override
@@ -80,12 +80,12 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
return false;
}
BeanOverrideContextCustomizer that = (BeanOverrideContextCustomizer) other;
return this.metadata.equals(that.metadata);
return this.handlers.equals(that.handlers);
}
@Override
public int hashCode() {
return this.metadata.hashCode();
return this.handlers.hashCode();
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.test.context.TestContextAnnotationUtils;
*
* @author Simon Baslé
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
* @see BeanOverride
*/
@@ -41,19 +42,18 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
public BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
Set<OverrideMetadata> metadata = new HashSet<>();
findOverrideMetadata(testClass, metadata);
if (metadata.isEmpty()) {
Set<BeanOverrideHandler> handlers = new HashSet<>();
findBeanOverrideHandler(testClass, handlers);
if (handlers.isEmpty()) {
return null;
}
return new BeanOverrideContextCustomizer(metadata);
return new BeanOverrideContextCustomizer(handlers);
}
private void findOverrideMetadata(Class<?> testClass, Set<OverrideMetadata> metadata) {
List<OverrideMetadata> overrideMetadata = OverrideMetadata.forTestClass(testClass);
metadata.addAll(overrideMetadata);
private void findBeanOverrideHandler(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
handlers.addAll(BeanOverrideHandler.forTestClass(testClass));
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findOverrideMetadata(testClass.getEnclosingClass(), metadata);
findBeanOverrideHandler(testClass.getEnclosingClass(), handlers);
}
}

View File

@@ -41,16 +41,21 @@ import org.springframework.util.ReflectionUtils;
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT;
/**
* Metadata for Bean Override injection points, also responsible for creation of
* the overriding instance.
* Handler for Bean Override injection points that is responsible for creating
* the bean override instance for a given set of metadata and potentially for
* tracking the created instance.
*
* <p><strong>WARNING</strong>: implementations are used as a cache key and
* must implement proper {@code equals()} and {@code hashCode()} methods.
* <p><strong>WARNING</strong>: Implementations are used as a cache key and must
* implement proper {@code equals()} and {@code hashCode()} methods based on the
* unique set of metadata used to identify the bean to override. Overridden
* {@code equals()} and {@code hashCode()} methods should also delegate to the
* {@code super} implementations in this class in order to support the basic
* metadata used by all bean overrides.
*
* <p>Concrete implementations of {@code OverrideMetadata} can store state to use
* during override {@linkplain #createOverride(String, BeanDefinition, Object)
* instance creation} &mdash; for example, based on further processing of the
* annotation or the annotated field.
* <p>Concrete implementations of {@code BeanOverrideHandler} can store additional
* metadata to use during override {@linkplain #createOverrideInstance instance
* creation} &mdash; for example, based on further processing of the annotation
* or the annotated field.
*
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
* Any attempt to override a non-singleton bean will result in an exception.
@@ -60,7 +65,7 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
* @author Sam Brannen
* @since 6.2
*/
public abstract class OverrideMetadata {
public abstract class BeanOverrideHandler {
private final Field field;
@@ -74,7 +79,7 @@ public abstract class OverrideMetadata {
private final BeanOverrideStrategy strategy;
protected OverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName,
protected BeanOverrideHandler(Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy) {
this.field = field;
@@ -85,19 +90,19 @@ public abstract class OverrideMetadata {
}
/**
* Process the given {@code testClass} and build the corresponding list of
* {@code OverrideMetadata} derived from {@link BeanOverride @BeanOverride}
* fields in the test class and its type hierarchy.
* Process the given {@code testClass} and build the corresponding
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
* fields in the test class, its type hierarchy, and its enclosing class hierarchy.
* @param testClass the test class to process
* @return a list of {@code OverrideMetadata}
* @return a list of bean override handlers
*/
public static List<OverrideMetadata> forTestClass(Class<?> testClass) {
List<OverrideMetadata> metadata = new LinkedList<>();
ReflectionUtils.doWithFields(testClass, field -> processField(field, testClass, metadata));
return metadata;
public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
List<BeanOverrideHandler> handlers = new LinkedList<>();
ReflectionUtils.doWithFields(testClass, field -> processField(field, testClass, handlers));
return handlers;
}
private static void processField(Field field, Class<?> testClass, List<OverrideMetadata> metadataList) {
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
@@ -109,8 +114,8 @@ public abstract class OverrideMetadata {
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
() -> "Multiple @BeanOverride annotations found on field: " + field);
OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field);
metadataList.add(metadata);
BeanOverrideHandler handler = processor.createHandler(composedAnnotation, testClass, field);
handlers.add(handler);
});
}
@@ -139,7 +144,7 @@ public abstract class OverrideMetadata {
}
/**
* Get the {@link BeanOverrideStrategy} for this {@code OverrideMetadata},
* Get the {@link BeanOverrideStrategy} for this {@code BeanOverrideHandler},
* which influences how and when the bean override instance should be created.
*/
public final BeanOverrideStrategy getStrategy() {
@@ -147,28 +152,31 @@ public abstract class OverrideMetadata {
}
/**
* Create a bean override instance for this {@code OverrideMetadata},
* optionally based on an existing {@link BeanDefinition} and/or an
* original instance, that is a singleton or an early wrapped instance.
* Create a bean override instance for an existing {@link BeanDefinition} or
* an existing singleton bean, based on the metadata in this
* {@code BeanOverrideHandler}.
* @param beanName the name of the bean being overridden
* @param existingBeanDefinition an existing bean definition for the supplied
* bean name, or {@code null} if irrelevant
* bean name, or {@code null} if not available or not relevant
* @param existingBeanInstance an existing instance for the supplied bean name
* for wrapping purposes, or {@code null} if irrelevant
* for wrapping purposes, or {@code null} if not available or not relevant
* @return the instance with which to override the bean
* @see #trackOverrideInstance(Object, SingletonBeanRegistry)
*/
protected abstract Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
@Nullable Object existingBeanInstance);
protected abstract Object createOverrideInstance(String beanName,
@Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance);
/**
* Optionally track objects created by this {@code OverrideMetadata}.
* Track the supplied bean override instance that was created by this
* {@code BeanOverrideHandler}.
* <p>The default implementation does not track the supplied instance, but
* this can be overridden in subclasses as appropriate.
* @param override the bean override instance to track
* @param trackingBeanRegistry the registry in which trackers can
* optionally be registered
* @param trackingBeanRegistry a registry in which this handler can store
* tracking state in the form of a singleton bean
* @see #createOverrideInstance(String, BeanDefinition, Object)
*/
protected void track(Object override, SingletonBeanRegistry trackingBeanRegistry) {
protected void trackOverrideInstance(Object override, SingletonBeanRegistry trackingBeanRegistry) {
// NO-OP
}
@@ -180,7 +188,7 @@ public abstract class OverrideMetadata {
if (other == null || other.getClass() != getClass()) {
return false;
}
OverrideMetadata that = (OverrideMetadata) other;
BeanOverrideHandler that = (BeanOverrideHandler) other;
if (!Objects.equals(this.beanType.getType(), that.beanType.getType()) ||
!Objects.equals(this.beanName, that.beanName) ||
!Objects.equals(this.strategy, that.strategy)) {

View File

@@ -20,33 +20,33 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
/**
* Strategy interface for Bean Override processing, providing
* {@link OverrideMetadata} that drives how the target bean is overridden.
* Strategy interface for Bean Override processing, which creates a
* {@link BeanOverrideHandler} that drives how the target bean is overridden.
*
* <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
* {@code OverrideMetadata} is configured.
* may optionally provide annotation attributes that can be used to configure the
* {@code BeanOverrideHandler}.
*
* <p>Implementations are required to have a no-argument constructor and be
* stateless.
*
* @author Simon Baslé
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
@FunctionalInterface
public interface BeanOverrideProcessor {
/**
* Create an {@link OverrideMetadata} instance for the given annotated field.
* Create a {@link BeanOverrideHandler} for the given annotated field.
* @param overrideAnnotation the composed annotation that declares the
* {@link BeanOverride @BeanOverride} annotation which registers this processor
* @param testClass the test class to process
* @param field the annotated field
* @return the {@link OverrideMetadata} instance that should handle the
* given field
* @return the {@code BeanOverrideHandler} that should handle the given field
*/
OverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field);
BeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field);
}

View File

@@ -28,7 +28,7 @@ import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* An internal class used to track {@link OverrideMetadata}-related state after
* An internal class used to track {@link BeanOverrideHandler}-related state after
* the bean factory has been processed and to provide field injection utilities
* for test execution listeners.
*
@@ -38,9 +38,9 @@ import org.springframework.util.StringUtils;
*/
class BeanOverrideRegistrar {
private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
private final Map<BeanOverrideHandler, String> beanNameRegistry = new HashMap<>();
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
private final Map<String, BeanOverrideHandler> wrappingBeanOverrideHandlers = new HashMap<>();
private final ConfigurableBeanFactory beanFactory;
@@ -51,40 +51,42 @@ class BeanOverrideRegistrar {
}
/**
* Check {@linkplain #markWrapEarly(OverrideMetadata, String) early override}
* records and use the {@link OverrideMetadata} to create an override
* instance based on the provided bean, if relevant.
* Register the provided {@link BeanOverrideHandler} and associate it with the
* given {@code beanName}.
*/
void registerBeanOverrideHandler(BeanOverrideHandler handler, String beanName) {
this.beanNameRegistry.put(handler, beanName);
}
/**
* Register the provided {@link BeanOverrideHandler} as a
* {@linkplain BeanOverrideStrategy#WRAP "wrapping"} handler and associate it
* with the given {@code beanName}, allowing for subsequent wrapping of the
* bean via {@link #wrapIfNecessary(Object, String)}.
*/
void registerWrappingBeanOverrideHandler(BeanOverrideHandler handler, String beanName) {
this.wrappingBeanOverrideHandlers.put(beanName, handler);
}
/**
* Check {@linkplain #registerWrappingBeanOverrideHandler(BeanOverrideHandler, String)
* wrapping handler} records and use the corresponding {@link BeanOverrideHandler}
* to create an override instance by wrapping the provided bean, if relevant.
*/
Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP) {
bean = metadata.createOverride(beanName, null, bean);
metadata.track(bean, this.beanFactory);
BeanOverrideHandler handler = this.wrappingBeanOverrideHandlers.get(beanName);
if (handler != null && handler.getStrategy() == BeanOverrideStrategy.WRAP) {
bean = handler.createOverrideInstance(beanName, null, bean);
handler.trackOverrideInstance(bean, this.beanFactory);
}
return bean;
}
/**
* Register the provided {@link OverrideMetadata} and associate it with the
* supplied {@code beanName}.
*/
void registerNameForMetadata(OverrideMetadata metadata, String beanName) {
this.beanNameRegistry.put(metadata, beanName);
}
/**
* Mark the provided {@link OverrideMetadata} and {@code beanName} as "wrap
* early", allowing for later bean override using {@link #wrapIfNecessary(Object, String)}.
*/
void markWrapEarly(OverrideMetadata metadata, String beanName) {
this.earlyOverrideMetadata.put(beanName, metadata);
}
void inject(Object target, OverrideMetadata overrideMetadata) {
String beanName = this.beanNameRegistry.get(overrideMetadata);
void inject(Object target, BeanOverrideHandler handler) {
String beanName = this.beanNameRegistry.get(handler);
Assert.state(StringUtils.hasLength(beanName),
() -> "No bean found for OverrideMetadata: " + overrideMetadata);
inject(overrideMetadata.getField(), target, beanName);
() -> "No bean found for BeanOverrideHandler: " + handler);
inject(handler.getField(), target, beanName);
}
private void inject(Field field, Object target, String beanName) {

View File

@@ -77,14 +77,14 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
* a corresponding bean override instance.
*/
private static void injectFields(TestContext testContext) {
List<OverrideMetadata> overrideMetadataList = OverrideMetadata.forTestClass(testContext.getTestClass());
if (!overrideMetadataList.isEmpty()) {
List<BeanOverrideHandler> handlerList = BeanOverrideHandler.forTestClass(testContext.getTestClass());
if (!handlerList.isEmpty()) {
Object testInstance = testContext.getTestInstance();
BeanOverrideRegistrar registrar = testContext.getApplicationContext()
.getBean(BeanOverrideContextCustomizer.REGISTRAR_BEAN_NAME, BeanOverrideRegistrar.class);
for (OverrideMetadata overrideMetadata : overrideMetadataList) {
registrar.inject(testInstance, overrideMetadata);
for (BeanOverrideHandler handler : handlerList) {
registrar.inject(testInstance, handler);
}
}
}

View File

@@ -24,41 +24,41 @@ import java.util.Objects;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.util.ReflectionUtils;
/**
* {@link OverrideMetadata} implementation for {@link TestBean}.
* {@link BeanOverrideHandler} implementation for {@link TestBean}.
*
* @author Simon Baslé
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
final class TestBeanOverrideMetadata extends OverrideMetadata {
final class TestBeanBeanOverrideHandler extends BeanOverrideHandler {
private final Method overrideMethod;
private final Method factoryMethod;
TestBeanOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy, Method overrideMethod) {
TestBeanBeanOverrideHandler(Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy, Method factoryMethod) {
super(field, beanType, beanName, strategy);
this.overrideMethod = overrideMethod;
this.factoryMethod = factoryMethod;
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
protected Object createOverrideInstance(String beanName, @Nullable BeanDefinition existingBeanDefinition,
@Nullable Object existingBeanInstance) {
try {
ReflectionUtils.makeAccessible(this.overrideMethod);
return this.overrideMethod.invoke(null);
ReflectionUtils.makeAccessible(this.factoryMethod);
return this.factoryMethod.invoke(null);
}
catch (IllegalAccessException | InvocationTargetException ex) {
throw new IllegalStateException("Failed to invoke bean overriding method " + this.overrideMethod.getName() +
throw new IllegalStateException("Failed to invoke bean overriding method " + this.factoryMethod.getName() +
"; a static method with no formal parameters is expected", ex);
}
}
@@ -74,13 +74,13 @@ final class TestBeanOverrideMetadata extends OverrideMetadata {
if (!super.equals(other)) {
return false;
}
TestBeanOverrideMetadata that = (TestBeanOverrideMetadata) other;
return Objects.equals(this.overrideMethod, that.overrideMethod);
TestBeanBeanOverrideHandler that = (TestBeanBeanOverrideHandler) other;
return Objects.equals(this.factoryMethod, that.factoryMethod);
}
@Override
public int hashCode() {
return this.overrideMethod.hashCode() * 29 + super.hashCode();
return this.factoryMethod.hashCode() * 29 + super.hashCode();
}
}

View File

@@ -42,9 +42,9 @@ import static org.springframework.test.context.bean.override.BeanOverrideStrateg
/**
* {@link BeanOverrideProcessor} implementation for {@link TestBean @TestBean}
* support, which creates metadata for annotated fields in a given class and
* ensures that a corresponding static factory method exists, according to the
* {@linkplain TestBean documented conventions}.
* support, which creates a {@link TestBeanBeanOverrideHandler} for annotated
* fields in a given class and ensures that a corresponding static factory method
* exists, according to the {@linkplain TestBean documented conventions}.
*
* @author Simon Baslé
* @author Sam Brannen
@@ -54,7 +54,7 @@ import static org.springframework.test.context.bean.override.BeanOverrideStrateg
class TestBeanOverrideProcessor implements BeanOverrideProcessor {
@Override
public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
public TestBeanBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (!(overrideAnnotation instanceof TestBean testBean)) {
throw new IllegalStateException("Invalid annotation passed to %s: expected @TestBean on field %s.%s"
.formatted(getClass().getSimpleName(), field.getDeclaringClass().getName(), field.getName()));
@@ -64,10 +64,10 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
String methodName = testBean.methodName();
BeanOverrideStrategy strategy = (testBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE);
Method overrideMethod;
Method factoryMethod;
if (!methodName.isBlank()) {
// If the user specified an explicit method name, search for that.
overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName);
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName);
}
else {
// Otherwise, search for candidate factory methods whose names match either
@@ -78,11 +78,11 @@ class TestBeanOverrideProcessor implements BeanOverrideProcessor {
if (beanName != null) {
candidateMethodNames.add(beanName);
}
overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
factoryMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
}
return new TestBeanOverrideMetadata(
field, ResolvableType.forField(field, testClass), beanName, strategy, overrideMethod);
return new TestBeanBeanOverrideHandler(
field, ResolvableType.forField(field, testClass), beanName, strategy, factoryMethod);
}
/**

View File

@@ -23,25 +23,25 @@ import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata;
/**
* Abstract base {@link OverrideMetadata} implementation for Mockito.
* Abstract base {@link BeanOverrideHandler} implementation for Mockito.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
abstract class AbstractMockitoOverrideMetadata extends OverrideMetadata {
abstract class AbstractMockitoBeanOverrideHandler extends BeanOverrideHandler {
private final MockReset reset;
private final boolean proxyTargetAware;
protected AbstractMockitoOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName,
protected AbstractMockitoBeanOverrideHandler(Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) {
super(field, beanType, beanName, strategy);
@@ -67,7 +67,7 @@ abstract class AbstractMockitoOverrideMetadata extends OverrideMetadata {
}
@Override
protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
protected void trackOverrideInstance(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
getMockitoBeans(trackingBeanRegistry).add(mock);
}
@@ -89,7 +89,7 @@ abstract class AbstractMockitoOverrideMetadata extends OverrideMetadata {
if (other == this) {
return true;
}
return (other instanceof AbstractMockitoOverrideMetadata that && super.equals(that) &&
return (other instanceof AbstractMockitoBeanOverrideHandler that && super.equals(that) &&
(this.reset == that.reset) && (this.proxyTargetAware == that.proxyTargetAware));
}

View File

@@ -31,8 +31,8 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -41,14 +41,14 @@ import static org.springframework.test.context.bean.override.BeanOverrideStrateg
import static org.springframework.test.context.bean.override.BeanOverrideStrategy.REPLACE_OR_CREATE;
/**
* {@link OverrideMetadata} implementation for Mockito {@code mock} support.
* {@link BeanOverrideHandler} implementation for Mockito {@code mock} support.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2
*/
class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
class MockitoBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
private final Set<Class<?>> extraInterfaces;
@@ -57,13 +57,13 @@ class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
private final boolean serializable;
MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
this(field, typeToMock, (!mockitoBean.name().isBlank() ? mockitoBean.name() : null),
(mockitoBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE),
mockitoBean.reset(), mockitoBean.extraInterfaces(), mockitoBean.answers(), mockitoBean.serializable());
}
private MockitoBeanOverrideMetadata(Field field, ResolvableType typeToMock, @Nullable String beanName,
private MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, @Nullable String beanName,
BeanOverrideStrategy strategy, MockReset reset, Class<?>[] extraInterfaces, @Nullable Answers answers,
boolean serializable) {
@@ -109,7 +109,7 @@ class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
}
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
protected Object createOverrideInstance(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
return createMock(beanName);
}
@@ -138,7 +138,7 @@ class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
if (other == null || other.getClass() != getClass()) {
return false;
}
return (other instanceof MockitoBeanOverrideMetadata that && super.equals(that) &&
return (other instanceof MockitoBeanOverrideHandler that && super.equals(that) &&
(this.serializable == that.serializable) && (this.answers == that.answers) &&
Objects.equals(this.extraInterfaces, that.extraInterfaces));
}

View File

@@ -34,12 +34,12 @@ import org.springframework.test.context.bean.override.BeanOverrideProcessor;
class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
@Override
public AbstractMockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (overrideAnnotation instanceof MockitoBean mockBean) {
return new MockitoBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), mockBean);
return new MockitoBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockBean);
}
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), spyBean);
return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), spyBean);
}
throw new IllegalStateException("""
Invalid annotation passed to MockitoBeanOverrideProcessor: \

View File

@@ -28,28 +28,28 @@ import org.mockito.listeners.VerificationStartedListener;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
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.StringUtils;
/**
* {@link OverrideMetadata} implementation for Mockito {@code spy} support.
* {@link BeanOverrideHandler} implementation for Mockito {@code spy} support.
*
* @author Phillip Webb
* @author Simon Baslé
* @author Stephane Nicoll
* @since 6.2
*/
class MockitoSpyBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) {
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) {
this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null),
spyAnnotation.reset(), spyAnnotation.proxyTargetAware());
}
MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, @Nullable String beanName,
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, @Nullable String beanName,
MockReset reset, boolean proxyTargetAware) {
super(field, typeToSpy, beanName, BeanOverrideStrategy.WRAP, reset, proxyTargetAware);
@@ -58,7 +58,7 @@ class MockitoSpyBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
@Override
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
protected Object createOverrideInstance(String beanName, @Nullable BeanDefinition existingBeanDefinition,
@Nullable Object existingBeanInstance) {
Assert.notNull(existingBeanInstance,