Skip to content

Commit bed3025

Browse files
committed
Restructure BeanOverrideBeanFactoryPostProcessor and improve documentation
1 parent ea8b8ae commit bed3025

File tree

2 files changed

+88
-73
lines changed

2 files changed

+88
-73
lines changed

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

+85-70
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,21 @@
4444

4545
/**
4646
* A {@link BeanFactoryPostProcessor} implementation that processes identified
47-
* use of {@link BeanOverride} and adapts the {@link BeanFactory} accordingly.
47+
* use of {@link BeanOverride @BeanOverride} and adapts the {@link BeanFactory}
48+
* accordingly.
4849
*
4950
* <p>For each override, the bean factory is prepared according to the chosen
50-
* {@link BeanOverrideStrategy overriding strategy}. The override value is created,
51+
* {@linkplain BeanOverrideStrategy override strategy}. The override value is created,
5152
* if necessary, and the necessary infrastructure is updated to allow the value
5253
* to be injected in the corresponding {@linkplain OverrideMetadata#getField() field}
5354
* of the test class.
5455
*
55-
* <p>This processor does not work against a particular test class, it only prepares
56-
* the bean factory for the identified, unique set of bean overrides.
56+
* <p>This processor does not work against a particular test class, but rather
57+
* only prepares the bean factory for the identified, unique set of bean overrides.
5758
*
5859
* @author Simon Baslé
5960
* @author Stephane Nicoll
61+
* @author Sam Brannen
6062
* @since 6.2
6163
*/
6264
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@@ -69,11 +71,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
6971

7072

7173
/**
72-
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with
73-
* the set of {@link OverrideMetadata} to process, using the given
74+
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied
75+
* set of {@link OverrideMetadata} to process, using the given
7476
* {@link BeanOverrideRegistrar}.
7577
* @param metadata the {@link OverrideMetadata} instances to process
76-
* @param overrideRegistrar the {@link BeanOverrideRegistrar} used to track
78+
* @param overrideRegistrar the {@code BeanOverrideRegistrar} used to track
7779
* metadata
7880
*/
7981
public BeanOverrideBeanFactoryPostProcessor(Set<OverrideMetadata> metadata,
@@ -84,21 +86,18 @@ public BeanOverrideBeanFactoryPostProcessor(Set<OverrideMetadata> metadata,
8486
}
8587

8688

89+
@Override
90+
public int getOrder() {
91+
return Ordered.LOWEST_PRECEDENCE - 10;
92+
}
93+
8794
@Override
8895
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
8996
if (!(beanFactory instanceof BeanDefinitionRegistry registry)) {
9097
throw new IllegalStateException("Cannot process bean override with a BeanFactory " +
9198
"that doesn't implement BeanDefinitionRegistry: " + beanFactory.getClass());
9299
}
93-
postProcessWithRegistry(beanFactory, registry);
94-
}
95100

96-
@Override
97-
public int getOrder() {
98-
return Ordered.LOWEST_PRECEDENCE - 10;
99-
}
100-
101-
private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
102101
for (OverrideMetadata metadata : this.metadata) {
103102
registerBeanOverride(beanFactory, registry, metadata);
104103
}
@@ -108,15 +107,13 @@ private void registerBeanOverride(ConfigurableListableBeanFactory beanFactory, B
108107
OverrideMetadata overrideMetadata) {
109108

110109
switch (overrideMetadata.getStrategy()) {
111-
case REPLACE_DEFINITION ->
112-
registerReplaceDefinition(beanFactory, registry, overrideMetadata, true);
113-
case REPLACE_OR_CREATE_DEFINITION ->
114-
registerReplaceDefinition(beanFactory, registry, overrideMetadata, false);
115-
case WRAP_BEAN -> registerWrapBean(beanFactory, overrideMetadata);
110+
case REPLACE_DEFINITION -> replaceDefinition(beanFactory, registry, overrideMetadata, true);
111+
case REPLACE_OR_CREATE_DEFINITION -> replaceDefinition(beanFactory, registry, overrideMetadata, false);
112+
case WRAP_BEAN -> wrapBean(beanFactory, overrideMetadata);
116113
}
117114
}
118115

119-
private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
116+
private void replaceDefinition(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
120117
OverrideMetadata overrideMetadata, boolean enforceExistingDefinition) {
121118

122119
// The following is a "dummy" bean definition which should not be used to
@@ -169,96 +166,116 @@ else if (enforceExistingDefinition) {
169166
}
170167
}
171168

172-
private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
173-
OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) {
174-
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
175-
int candidateCount = candidateNames.size();
176-
if (candidateCount == 1) {
177-
return candidateNames.iterator().next();
178-
}
179-
else if (candidateCount == 0) {
180-
if (enforceExistingDefinition) {
181-
Field field = overrideMetadata.getField();
182-
throw new IllegalStateException(
183-
"Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')"
184-
.formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
185-
}
186-
return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
187-
}
188-
Field field = overrideMetadata.getField();
189-
throw new IllegalStateException(String.format(
190-
"Unable to select a bean definition to override: found %s bean definitions of type %s " +
191-
"(as required by annotated field '%s.%s'): %s",
192-
candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
193-
field.getName(), candidateNames));
194-
}
195-
196169
/**
197170
* Check that the expected bean name is registered and matches the type to override.
198171
* <p>If so, put the override metadata in the early tracking map.
199172
* <p>The map will later be checked to see if a given bean should be wrapped
200173
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
201174
* phase.
202175
*/
203-
private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata) {
204-
String beanName = metadata.getBeanName();
176+
private void wrapBean(ConfigurableListableBeanFactory beanFactory, OverrideMetadata overrideMetadata) {
177+
String beanName = overrideMetadata.getBeanName();
205178
if (beanName == null) {
206-
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, metadata, true);
179+
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
207180
int candidateCount = candidateNames.size();
208181
if (candidateCount != 1) {
209-
Field field = metadata.getField();
182+
Field field = overrideMetadata.getField();
210183
throw new IllegalStateException("Unable to select a bean to override by wrapping: found " +
211-
candidateCount + " bean instances of type " + metadata.getBeanType() +
184+
candidateCount + " bean instances of type " + overrideMetadata.getBeanType() +
212185
" (as required by annotated field '" + field.getDeclaringClass().getSimpleName() +
213186
"." + field.getName() + "')" + (candidateCount > 0 ? ": " + candidateNames : ""));
214187
}
215188
beanName = BeanFactoryUtils.transformedBeanName(candidateNames.iterator().next());
216189
}
217190
else {
218-
Set<String> candidates = getExistingBeanNamesByType(beanFactory, metadata, false);
191+
Set<String> candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false);
219192
if (!candidates.contains(beanName)) {
220-
throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping: there is no " +
221-
"existing bean instance with that name of type " + metadata.getBeanType());
193+
throw new IllegalStateException("""
194+
Unable to override bean by wrapping: there is no existing bean definition \
195+
with name [%s] and type [%s]."""
196+
.formatted(beanName, overrideMetadata.getBeanType()));
197+
}
198+
}
199+
this.overrideRegistrar.markWrapEarly(overrideMetadata, beanName);
200+
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanName);
201+
}
202+
203+
private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
204+
OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) {
205+
206+
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
207+
int candidateCount = candidateNames.size();
208+
if (candidateCount == 1) {
209+
return candidateNames.iterator().next();
210+
}
211+
else if (candidateCount == 0) {
212+
if (enforceExistingDefinition) {
213+
Field field = overrideMetadata.getField();
214+
throw new IllegalStateException(
215+
"Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')"
216+
.formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
222217
}
218+
return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
223219
}
224-
this.overrideRegistrar.markWrapEarly(metadata, beanName);
225-
this.overrideRegistrar.registerNameForMetadata(metadata, beanName);
220+
221+
Field field = overrideMetadata.getField();
222+
throw new IllegalStateException("""
223+
Unable to select a bean definition to override: found %s bean definitions of type %s \
224+
(as required by annotated field '%s.%s'): %s"""
225+
.formatted(candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
226+
field.getName(), candidateNames));
226227
}
227228

228229
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, OverrideMetadata metadata,
229230
boolean checkAutowiredCandidate) {
230231

231232
ResolvableType resolvableType = metadata.getBeanType();
232-
Set<String> beans = new LinkedHashSet<>(
233-
Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
234233
Class<?> type = resolvableType.resolve(Object.class);
234+
235+
// Start with matching bean names for type, excluding FactoryBeans.
236+
Set<String> beanNames = new LinkedHashSet<>(
237+
Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
238+
239+
// Add matching FactoryBeans as well.
235240
for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) {
236241
beanName = BeanFactoryUtils.transformedBeanName(beanName);
237242
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
238243
Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
239244
if (resolvableType.equals(attribute) || type.equals(attribute)) {
240-
beans.add(beanName);
245+
beanNames.add(beanName);
241246
}
242247
}
248+
249+
// Filter out non-matching autowire candidates.
243250
if (checkAutowiredCandidate) {
244251
DependencyDescriptor descriptor = new DependencyDescriptor(metadata.getField(), true);
245-
beans.removeIf(beanName -> ScopedProxyUtils.isScopedTarget(beanName) ||
246-
!beanFactory.isAutowireCandidate(beanName, descriptor));
252+
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
247253
}
248-
else {
249-
beans.removeIf(ScopedProxyUtils::isScopedTarget);
250-
}
251-
// In case of multiple matches, last resort fallback on the field's name
252-
if (beans.size() > 1) {
254+
// Filter out scoped proxy targets.
255+
beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
256+
257+
// In case of multiple matches, fall back on the field's name as a last resort.
258+
if (beanNames.size() > 1) {
253259
String fieldName = metadata.getField().getName();
254-
if (beans.contains(fieldName)) {
260+
if (beanNames.contains(fieldName)) {
255261
return Set.of(fieldName);
256262
}
257263
}
258-
return beans;
264+
return beanNames;
259265
}
260266

261-
267+
/**
268+
* Create a pseudo-{@link BeanDefinition} for the supplied {@link OverrideMetadata},
269+
* whose {@linkplain RootBeanDefinition#getTargetType() target type} and
270+
* {@linkplain RootBeanDefinition#getQualifiedElement() qualified element} are
271+
* the {@linkplain OverrideMetadata#getBeanType() bean type} and
272+
* the {@linkplain OverrideMetadata#getField() field} of the {@code OverrideMetadata},
273+
* respectively.
274+
* <p>The returned bean definition should <strong>not</strong> be used to create
275+
* a bean instance but rather only for the purpose of having suitable bean
276+
* definition metadata available in the {@link BeanFactory} &mdash; for example,
277+
* for autowiring candidate resolution.
278+
*/
262279
private static RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
263280
RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve());
264281
definition.setTargetType(metadata.getBeanType());
@@ -286,12 +303,10 @@ static class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPo
286303

287304
private final BeanOverrideRegistrar overrideRegistrar;
288305

289-
290306
WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) {
291307
this.overrideRegistrar = registrar;
292308
}
293309

294-
295310
@Override
296311
public int getOrder() {
297312
return Ordered.HIGHEST_PRECEDENCE;

spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanTests.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ void contextCustomizerCannotBeCreatedWithNoSuchBeanName() {
4040
assertThatIllegalStateException()
4141
.isThrownBy(context::refresh)
4242
.withMessage("""
43-
Unable to override bean 'beanToSpy' by wrapping: \
44-
there is no existing bean instance with that name of type %s""".formatted(
45-
String.class.getName()));
43+
Unable to override bean by wrapping: \
44+
there is no existing bean definition with name [beanToSpy] and type [%s].""",
45+
String.class.getName());
4646
}
4747

4848
@Test

0 commit comments

Comments
 (0)