Ensure @⁠BeanOverride in subclass takes precedence over superclass

Prior to this commit, a @⁠BeanOverride (such as @⁠TestBean) for a
specific target bean which was declared in a superclass always took
precedence over a bean override for the same target bean in a subclass,
thereby rendering the bean override configuration in the subclass
useless. In other words, there was no way for a test class to override
a bean override declared in a superclass.

To address that, this commit switches from direct use of
ReflectionUtils.doWithFields() to a custom search algorithm that
traverses the class hierarchy using tail recursion for processing
@⁠BeanOverride fields (delegating now to
ReflectionUtils.doWithLocalFields() in order to continue to benefit
from the caching of declared fields in ReflectionUtils).

Closes gh-34194
This commit is contained in:
Sam Brannen
2025-01-04 18:19:18 +02:00
parent 51b89743e1
commit ef4f1f0a71
2 changed files with 103 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -103,10 +103,27 @@ public abstract class BeanOverrideHandler {
*/
public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
List<BeanOverrideHandler> handlers = new LinkedList<>();
ReflectionUtils.doWithFields(testClass, field -> processField(field, testClass, handlers));
findHandlers(testClass, testClass, handlers);
return handlers;
}
/**
* Find handlers using tail recursion to ensure that "locally declared"
* bean overrides take precedence over inherited bean overrides.
* @since 6.2.2
*/
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers) {
if (clazz == null || clazz == Object.class) {
return;
}
// 1) Search type hierarchy.
findHandlers(clazz.getSuperclass(), testClass, handlers);
// 2) Process fields in current class.
ReflectionUtils.doWithLocalFields(clazz, field -> processField(field, testClass, handlers));
}
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {