Skip to content

Commit 2e3a923

Browse files
committed
Polish
1 parent 77c6f16 commit 2e3a923

16 files changed

+228
-228
lines changed

framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beanoverriding.adoc

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Java::
4848
======
4949

5050
NOTE: The method to invoke is searched in the test class and any enclosing class it might
51-
have, as well as its hierarchy. This typically allows nested test class to provide the
51+
have, as well as its hierarchy. This typically allows nested test class to rely on the
5252
method to use in the root test class.
5353

5454
[[spring-testing-annotation-beanoverriding-mockitobean]]
@@ -101,33 +101,34 @@ Java::
101101
The three annotations introduced above build upon the `@BeanOverride` meta-annotation
102102
and associated infrastructure, which allows to define custom bean overriding variants.
103103

104-
In order to provide an extension, three classes are needed:
104+
To create an extension, the following is needed:
105105

106-
- A concrete `BeanOverrideProcessor` implementation, `P`.
107-
- One or more concrete `OverrideMetadata` implementations created by said processor.
108-
- An annotation meta-annotated with `@BeanOverride(P.class)`.
106+
- An annotation meta-annotated with `@BeanOverride` that defines the
107+
`BeanOverrideProcessor` to use.
108+
- The `BeanOverrideProcessor` implementation itself.
109+
- One or more concrete `OverrideMetadata` implementations provided by the processor.
109110

110111
The Spring TestContext Framework includes infrastructure classes that support bean
111-
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a `ContextCustomizerFactory`.
112+
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a
113+
`ContextCustomizerFactory`.
112114
The later two are automatically registered via the Spring TestContext Framework
113115
`spring.factories` file, and are responsible for setting up the rest of the infrastructure.
114116

115117
The test classes are parsed looking for any field meta-annotated with `@BeanOverride`,
116-
instantiating the relevant `BeanOverrideProcessor` in order to register an `OverrideMetadata`.
118+
instantiating the relevant `BeanOverrideProcessor` in order to register an
119+
`OverrideMetadata`.
117120

118121
Then the `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the
119-
Context, registering and replacing bean definitions as influenced by each metadata
122+
context, registering and replacing bean definitions as defined by each metadata
120123
`BeanOverrideStrategy`:
121124

122-
- `REPLACE_DEFINITION`: the bean post-processor replaces the bean definition.
123-
If it is not present in the context, an exception is thrown.
124-
- `CREATE_OR_REPLACE_DEFINITION`: same as above but if the bean definition is not present
125-
in the context, one is created
126-
- `WRAP_EARLY_BEAN`: an original instance is obtained and passed to the `OverrideMetadata`
127-
when the override instance is created.
128-
129-
NOTE: The Bean Overriding infrastructure doesn't include any bean resolution step
130-
(unlike e.g. an `@Autowired`-annotated field). As such, the name of the bean to override
131-
MUST be somehow provided to or computed by the `BeanOverrideProcessor`. Typically, the end
132-
user provides the name as part of the custom annotation's attributes, or the annotated
133-
field's name.
125+
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the
126+
context, an exception is thrown.
127+
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition
128+
does not exist, or create one if it is not.
129+
- `WRAP_BEAN`: get the original instance early so that it can be wrapped.
130+
131+
NOTE: The Bean Overriding infrastructure does not include any bean resolution step,
132+
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to
133+
override must be somehow provided to or computed by the `BeanOverrideProcessor`.
134+
Typically, the user provides the name one way or the other.

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@
2222
import java.lang.annotation.Target;
2323

2424
/**
25-
* Mark an annotation as eligible for Bean Override parsing.
25+
* Mark an annotation as eligible for Bean Override processing.
2626
*
27-
* <p>This meta-annotation specifies a {@link BeanOverrideProcessor} class which
28-
* must be capable of handling the composed annotation that is meta-annotated
29-
* with {@code @BeanOverride}.
27+
* <p>Specifying this annotation triggers the defined {@link BeanOverrideProcessor}
28+
* which must be capable of handling the composed annotation and its attributes.
3029
*
31-
* <p>The composed annotation that is meta-annotated with {@code @BeanOverride}
32-
* must have a {@code RetentionPolicy} of {@link RetentionPolicy#RUNTIME RUNTIME}
33-
* and a {@code Target} of {@link ElementType#FIELD FIELD}.
30+
* <p>The composed annotation is meant to be detected on fields only so it is
31+
* expected that it has a {@code Target} of {@link ElementType#FIELD FIELD}.
3432
*
3533
* @author Simon Baslé
3634
* @since 6.2
@@ -41,8 +39,8 @@
4139
public @interface BeanOverride {
4240

4341
/**
44-
* A {@link BeanOverrideProcessor} implementation class by which the composed
45-
* annotation should be processed.
42+
* The {@link BeanOverrideProcessor} implementation to trigger against
43+
* the composed annotation.
4644
*/
4745
Class<? extends BeanOverrideProcessor> value();
4846

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java

Lines changed: 22 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,26 @@
2222
import java.util.Map;
2323
import java.util.Set;
2424
import java.util.concurrent.ConcurrentHashMap;
25-
import java.util.function.Consumer;
2625

2726
import org.springframework.aop.scope.ScopedProxyUtils;
2827
import org.springframework.beans.BeansException;
28+
import org.springframework.beans.factory.BeanFactory;
2929
import org.springframework.beans.factory.BeanFactoryUtils;
3030
import org.springframework.beans.factory.FactoryBean;
3131
import org.springframework.beans.factory.config.BeanDefinition;
3232
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3333
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
34-
import org.springframework.beans.factory.config.ConstructorArgumentValues;
35-
import org.springframework.beans.factory.config.RuntimeBeanReference;
3634
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
3735
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
38-
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3936
import org.springframework.beans.factory.support.RootBeanDefinition;
4037
import org.springframework.core.Ordered;
4138
import org.springframework.core.PriorityOrdered;
4239
import org.springframework.core.ResolvableType;
43-
import org.springframework.util.Assert;
4440
import org.springframework.util.StringUtils;
4541

4642
/**
4743
* A {@link BeanFactoryPostProcessor} implementation that processes test classes
48-
* and adapt the {@link BeanDefinitionRegistry} for any {@link BeanOverride} it
49-
* may define.
44+
* and adapt the {@link BeanFactory} for any {@link BeanOverride} it may define.
5045
*
5146
* <p>A set of classes from which to parse {@link OverrideMetadata} must be
5247
* provided to this processor. Each test class is expected to use any
@@ -66,10 +61,6 @@
6661
*/
6762
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
6863

69-
private static final String INFRASTRUCTURE_BEAN_NAME = BeanOverrideBeanFactoryPostProcessor.class.getName();
70-
71-
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME =
72-
BeanOverrideBeanFactoryPostProcessor.WrapEarlyBeanPostProcessor.class.getName();
7364

7465
private final BeanOverrideRegistrar overrideRegistrar;
7566

@@ -94,14 +85,16 @@ public int getOrder() {
9485

9586
@Override
9687
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
97-
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory,
98-
"Bean overriding annotations can only be used on a DefaultListableBeanFactory");
99-
postProcessWithRegistry((DefaultListableBeanFactory) beanFactory);
88+
if (!(beanFactory instanceof BeanDefinitionRegistry registry)) {
89+
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
90+
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass());
91+
}
92+
postProcessWithRegistry(beanFactory, registry);
10093
}
10194

102-
private void postProcessWithRegistry(DefaultListableBeanFactory registry) {
95+
private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
10396
for (OverrideMetadata metadata : this.overrideRegistrar.getOverrideMetadata()) {
104-
registerBeanOverride(registry, metadata);
97+
registerBeanOverride(beanFactory, registry, metadata);
10598
}
10699
}
107100

@@ -116,15 +109,20 @@ protected void copyBeanDefinitionDetails(BeanDefinition from, RootBeanDefinition
116109
to.setScope(from.getScope());
117110
}
118111

119-
private void registerBeanOverride(DefaultListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
112+
private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
113+
OverrideMetadata overrideMetadata) {
114+
120115
switch (overrideMetadata.getStrategy()) {
121-
case REPLACE_DEFINITION -> registerReplaceDefinition(beanFactory, overrideMetadata, true);
122-
case REPLACE_OR_CREATE_DEFINITION -> registerReplaceDefinition(beanFactory, overrideMetadata, false);
116+
case REPLACE_DEFINITION ->
117+
registerReplaceDefinition(beanFactory, registry, overrideMetadata, true);
118+
case REPLACE_OR_CREATE_DEFINITION ->
119+
registerReplaceDefinition(beanFactory, registry, overrideMetadata, false);
123120
case WRAP_BEAN -> registerWrapBean(beanFactory, overrideMetadata);
124121
}
125122
}
126123

127-
private void registerReplaceDefinition(DefaultListableBeanFactory beanFactory, OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
124+
private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
125+
OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
128126

129127
RootBeanDefinition beanDefinition = createBeanDefinition(overrideMetadata);
130128
String beanName = overrideMetadata.getBeanName();
@@ -133,13 +131,13 @@ private void registerReplaceDefinition(DefaultListableBeanFactory beanFactory, O
133131
if (beanFactory.containsBeanDefinition(beanName)) {
134132
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
135133
copyBeanDefinitionDetails(existingBeanDefinition, beanDefinition);
136-
beanFactory.removeBeanDefinition(beanName);
134+
registry.removeBeanDefinition(beanName);
137135
}
138136
else if (enforceExistingDefinition) {
139137
throw new IllegalStateException("Unable to override bean '" + beanName + "'; there is no" +
140138
" bean definition to replace with that name");
141139
}
142-
beanFactory.registerBeanDefinition(beanName, beanDefinition);
140+
registry.registerBeanDefinition(beanName, beanDefinition);
143141

144142
Object override = overrideMetadata.createOverride(beanName, existingBeanDefinition, null);
145143
if (beanFactory.isSingleton(beanName)) {
@@ -160,7 +158,7 @@ else if (enforceExistingDefinition) {
160158
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
161159
* phase.
162160
*/
163-
private void registerWrapBean(DefaultListableBeanFactory beanFactory, OverrideMetadata metadata) {
161+
private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata) {
164162
Set<String> existingBeanNames = getExistingBeanNames(beanFactory, metadata.getBeanType());
165163
String beanName = metadata.getBeanName();
166164
if (!existingBeanNames.contains(beanName)) {
@@ -177,7 +175,7 @@ private RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
177175
return definition;
178176
}
179177

180-
private Set<String> getExistingBeanNames(DefaultListableBeanFactory beanFactory, ResolvableType resolvableType) {
178+
private Set<String> getExistingBeanNames(ConfigurableListableBeanFactory beanFactory, ResolvableType resolvableType) {
181179
Set<String> beans = new LinkedHashSet<>(
182180
Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
183181
Class<?> type = resolvableType.resolve(Object.class);
@@ -193,39 +191,6 @@ private Set<String> getExistingBeanNames(DefaultListableBeanFactory beanFactory,
193191
return beans;
194192
}
195193

196-
/**
197-
* Register a {@link BeanOverrideBeanFactoryPostProcessor} with a {@link BeanDefinitionRegistry}.
198-
* <p>Not required when using the Spring TestContext Framework, as registration
199-
* is automatic via the
200-
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
201-
* mechanism.
202-
* @param registry the bean definition registry
203-
*/
204-
public static void register(BeanDefinitionRegistry registry) {
205-
RuntimeBeanReference registrarReference = new RuntimeBeanReference(BeanOverrideRegistrar.INFRASTRUCTURE_BEAN_NAME);
206-
// Early processor
207-
addInfrastructureBeanDefinition(
208-
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
209-
constructorArgs.addIndexedArgumentValue(0, registrarReference));
210-
211-
// Main processor
212-
addInfrastructureBeanDefinition(
213-
registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
214-
constructorArgs.addIndexedArgumentValue(0, registrarReference));
215-
}
216-
217-
private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
218-
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
219-
220-
if (!registry.containsBeanDefinition(beanName)) {
221-
RootBeanDefinition definition = new RootBeanDefinition(clazz);
222-
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
223-
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
224-
constructorArgumentsConsumer.accept(constructorArguments);
225-
registry.registerBeanDefinition(beanName, definition);
226-
}
227-
}
228-
229194

230195
static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
231196
PriorityOrdered {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.bean.override;
18+
19+
import java.util.Set;
20+
import java.util.function.Consumer;
21+
22+
import org.springframework.beans.factory.config.BeanDefinition;
23+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
24+
import org.springframework.beans.factory.config.RuntimeBeanReference;
25+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
26+
import org.springframework.beans.factory.support.RootBeanDefinition;
27+
import org.springframework.context.ConfigurableApplicationContext;
28+
import org.springframework.test.context.ContextCustomizer;
29+
import org.springframework.test.context.MergedContextConfiguration;
30+
import org.springframework.test.context.bean.override.BeanOverrideBeanFactoryPostProcessor.WrapEarlyBeanPostProcessor;
31+
32+
/**
33+
* {@link ContextCustomizer} implementation that registers the necessary
34+
* infrastructure to support {@linkplain BeanOverride bean overriding}.
35+
*
36+
* @author Simon Baslé
37+
* @author Stephane Nicoll
38+
* @since 6.2
39+
*/
40+
class BeanOverrideContextCustomizer implements ContextCustomizer {
41+
42+
private static final String REGISTRAR_BEAN_NAME =
43+
"org.springframework.test.context.bean.override.internalBeanOverrideRegistrar";
44+
45+
private static final String INFRASTRUCTURE_BEAN_NAME =
46+
"org.springframework.test.context.bean.override.internalBeanOverridePostProcessor";
47+
48+
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME =
49+
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
50+
51+
52+
private final Set<Class<?>> detectedClasses;
53+
54+
BeanOverrideContextCustomizer(Set<Class<?>> detectedClasses) {
55+
this.detectedClasses = detectedClasses;
56+
}
57+
58+
static void registerInfrastructure(BeanDefinitionRegistry registry, Set<Class<?>> detectedClasses) {
59+
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
60+
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, detectedClasses));
61+
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
62+
addInfrastructureBeanDefinition(
63+
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
64+
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
65+
addInfrastructureBeanDefinition(
66+
registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
67+
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
68+
}
69+
70+
private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
71+
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
72+
if (!registry.containsBeanDefinition(beanName)) {
73+
RootBeanDefinition definition = new RootBeanDefinition(clazz);
74+
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
75+
ConstructorArgumentValues constructorArguments = definition.getConstructorArgumentValues();
76+
constructorArgumentsConsumer.accept(constructorArguments);
77+
registry.registerBeanDefinition(beanName, definition);
78+
}
79+
}
80+
81+
@Override
82+
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
83+
if (context instanceof BeanDefinitionRegistry registry) {
84+
registerInfrastructure(registry, this.detectedClasses);
85+
}
86+
}
87+
88+
@Override
89+
public boolean equals(Object obj) {
90+
if (obj == this) {
91+
return true;
92+
}
93+
if (obj == null || obj.getClass() != getClass()) {
94+
return false;
95+
}
96+
BeanOverrideContextCustomizer other = (BeanOverrideContextCustomizer) obj;
97+
return this.detectedClasses.equals(other.detectedClasses);
98+
}
99+
100+
@Override
101+
public int hashCode() {
102+
return this.detectedClasses.hashCode();
103+
}
104+
105+
}

0 commit comments

Comments
 (0)