17
17
package org .springframework .test .context .bean .override ;
18
18
19
19
import java .lang .annotation .Annotation ;
20
+ import java .lang .reflect .AnnotatedElement ;
20
21
import java .lang .reflect .Field ;
21
22
import java .lang .reflect .Modifier ;
23
+ import java .util .ArrayList ;
22
24
import java .util .Arrays ;
23
25
import java .util .Collections ;
26
+ import java .util .Comparator ;
24
27
import java .util .HashSet ;
25
- import java .util .LinkedList ;
26
28
import java .util .List ;
27
29
import java .util .Objects ;
28
30
import java .util .Set ;
29
31
import java .util .concurrent .atomic .AtomicBoolean ;
32
+ import java .util .function .BiConsumer ;
33
+ import java .util .function .Predicate ;
30
34
31
35
import org .springframework .beans .BeanUtils ;
32
36
import org .springframework .beans .factory .config .BeanDefinition ;
57
61
*
58
62
* <p>Concrete implementations of {@code BeanOverrideHandler} can store additional
59
63
* metadata to use during override {@linkplain #createOverrideInstance instance
60
- * creation} — for example, based on further processing of the annotation
61
- * or the annotated field .
64
+ * creation} — for example, based on further processing of the annotation,
65
+ * the annotated field, or the annotated class .
62
66
*
63
67
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
64
68
* Any attempt to override a non-singleton bean will result in an exception.
70
74
*/
71
75
public abstract class BeanOverrideHandler {
72
76
77
+ @ Nullable
73
78
private final Field field ;
74
79
75
80
private final Set <Annotation > fieldAnnotations ;
@@ -82,7 +87,7 @@ public abstract class BeanOverrideHandler {
82
87
private final BeanOverrideStrategy strategy ;
83
88
84
89
85
- protected BeanOverrideHandler (Field field , ResolvableType beanType , @ Nullable String beanName ,
90
+ protected BeanOverrideHandler (@ Nullable Field field , ResolvableType beanType , @ Nullable String beanName ,
86
91
BeanOverrideStrategy strategy ) {
87
92
88
93
this .field = field ;
@@ -96,57 +101,121 @@ protected BeanOverrideHandler(Field field, ResolvableType beanType, @Nullable St
96
101
* Process the given {@code testClass} and build the corresponding
97
102
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
98
103
* fields in the test class and its type hierarchy.
99
- * <p>This method does not search the enclosing class hierarchy.
104
+ * <p>This method does not search the enclosing class hierarchy and does not
105
+ * search for {@code @BeanOverride} declarations on classes or interfaces.
100
106
* @param testClass the test class to process
101
107
* @return a list of bean override handlers
102
- * @see org.springframework.test.context.TestContextAnnotationUtils#searchEnclosingClass (Class)
108
+ * @see #forTestClass (Class, Predicate )
103
109
*/
104
110
public static List <BeanOverrideHandler > forTestClass (Class <?> testClass ) {
105
- List <BeanOverrideHandler > handlers = new LinkedList <>();
106
- findHandlers (testClass , testClass , handlers );
111
+ return findHandlers (testClass , false , clazz -> false );
112
+ }
113
+
114
+ /**
115
+ * Process the given {@code testClass} and build the corresponding
116
+ * {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
117
+ * fields in the test class and in its type hierarchy as well as from
118
+ * {@code @BeanOverride} declarations on classes and interfaces.
119
+ * <p>This method additionally searches for {@code @BeanOverride} declarations
120
+ * in the enclosing class hierarchy if the supplied predicate evaluates to
121
+ * {@code true}.
122
+ * @param testClass the test class to process
123
+ * @param searchEnclosingClass a predicate which evaluates to {@code true}
124
+ * if a search should be performed on the enclosing class — for example,
125
+ * {@code TestContextAnnotationUtils::searchEnclosingClass}
126
+ * @return a list of bean override handlers
127
+ * @since 6.2.2
128
+ * @see org.springframework.test.context.TestContextAnnotationUtils#searchEnclosingClass(Class)
129
+ */
130
+ public static List <BeanOverrideHandler > forTestClass (Class <?> testClass , Predicate <Class <?>> searchEnclosingClass ) {
131
+ return findHandlers (testClass , true , searchEnclosingClass );
132
+ }
133
+
134
+ private static List <BeanOverrideHandler > findHandlers (Class <?> testClass , boolean searchOnTypes ,
135
+ Predicate <Class <?>> searchEnclosingClass ) {
136
+
137
+ List <BeanOverrideHandler > handlers = new ArrayList <>();
138
+ findHandlers (testClass , testClass , handlers , searchOnTypes , searchEnclosingClass );
107
139
return handlers ;
108
140
}
109
141
110
142
/**
111
- * Find handlers using tail recursion to ensure that "locally declared"
112
- * bean overrides take precedence over inherited bean overrides.
143
+ * Find handlers using tail recursion to ensure that "locally declared" bean overrides
144
+ * take precedence over inherited bean overrides.
145
+ * <p>Note: the search algorithm is effectively the inverse of the algorithm used in
146
+ * {@link org.springframework.test.context.TestContextAnnotationUtils#findAnnotationDescriptor(Class, Class)},
147
+ * but with tail recursion the semantics should be the same.
113
148
* @since 6.2.2
114
149
*/
115
- private static void findHandlers (Class <?> clazz , Class <?> testClass , List <BeanOverrideHandler > handlers ) {
116
- if (clazz == null || clazz == Object .class ) {
117
- return ;
150
+ private static void findHandlers (Class <?> clazz , Class <?> testClass , List <BeanOverrideHandler > handlers ,
151
+ boolean searchOnTypes , Predicate <Class <?>> searchEnclosingClass ) {
152
+
153
+ // 1) Search enclosing class hierarchy.
154
+ if (searchEnclosingClass .test (clazz )) {
155
+ findHandlers (clazz .getEnclosingClass (), testClass , handlers , searchOnTypes , searchEnclosingClass );
118
156
}
119
157
120
- // 1) Search type hierarchy.
121
- findHandlers (clazz .getSuperclass (), testClass , handlers );
158
+ // 2) Search class hierarchy.
159
+ Class <?> superclass = clazz .getSuperclass ();
160
+ if (superclass != null && superclass != Object .class ) {
161
+ findHandlers (superclass , testClass , handlers , searchOnTypes , searchEnclosingClass );
162
+ }
163
+
164
+ if (searchOnTypes ) {
165
+ // 3) Search interfaces.
166
+ for (Class <?> ifc : clazz .getInterfaces ()) {
167
+ findHandlers (ifc , testClass , handlers , searchOnTypes , searchEnclosingClass );
168
+ }
169
+
170
+ // 4) Process current class.
171
+ processClass (clazz , testClass , handlers );
172
+ }
122
173
123
- // 2 ) Process fields in current class.
174
+ // 5 ) Process fields in current class.
124
175
ReflectionUtils .doWithLocalFields (clazz , field -> processField (field , testClass , handlers ));
125
176
}
126
177
178
+ private static void processClass (Class <?> clazz , Class <?> testClass , List <BeanOverrideHandler > handlers ) {
179
+ processElement (clazz , testClass , (processor , composedAnnotation ) ->
180
+ processor .createHandlers (composedAnnotation , testClass ).forEach (handlers ::add ));
181
+ }
182
+
127
183
private static void processField (Field field , Class <?> testClass , List <BeanOverrideHandler > handlers ) {
128
184
AtomicBoolean overrideAnnotationFound = new AtomicBoolean ();
129
- MergedAnnotations . from (field , DIRECT ). stream ( BeanOverride . class ). forEach ( mergedAnnotation -> {
185
+ processElement (field , testClass , ( processor , composedAnnotation ) -> {
130
186
Assert .state (!Modifier .isStatic (field .getModifiers ()),
131
187
() -> "@BeanOverride field must not be static: " + field );
132
- MergedAnnotation <?> metaSource = mergedAnnotation .getMetaSource ();
133
- Assert .state (metaSource != null , "@BeanOverride annotation must be meta-present" );
134
-
135
- BeanOverride beanOverride = mergedAnnotation .synthesize ();
136
- BeanOverrideProcessor processor = BeanUtils .instantiateClass (beanOverride .value ());
137
- Annotation composedAnnotation = metaSource .synthesize ();
138
-
139
188
Assert .state (overrideAnnotationFound .compareAndSet (false , true ),
140
189
() -> "Multiple @BeanOverride annotations found on field: " + field );
141
- BeanOverrideHandler handler = processor .createHandler (composedAnnotation , testClass , field );
142
- handlers .add (handler );
190
+ handlers .add (processor .createHandler (composedAnnotation , testClass , field ));
143
191
});
144
192
}
145
193
194
+ private static void processElement (AnnotatedElement element , Class <?> testClass ,
195
+ BiConsumer <BeanOverrideProcessor , Annotation > consumer ) {
196
+
197
+ MergedAnnotations .from (element , DIRECT )
198
+ .stream (BeanOverride .class )
199
+ .sorted (reversedMetaDistance )
200
+ .forEach (mergedAnnotation -> {
201
+ MergedAnnotation <?> metaSource = mergedAnnotation .getMetaSource ();
202
+ Assert .state (metaSource != null , "@BeanOverride annotation must be meta-present" );
203
+
204
+ BeanOverride beanOverride = mergedAnnotation .synthesize ();
205
+ BeanOverrideProcessor processor = BeanUtils .instantiateClass (beanOverride .value ());
206
+ Annotation composedAnnotation = metaSource .synthesize ();
207
+ consumer .accept (processor , composedAnnotation );
208
+ });
209
+ }
210
+
211
+ private static final Comparator <MergedAnnotation <? extends Annotation >> reversedMetaDistance =
212
+ Comparator .<MergedAnnotation <? extends Annotation >> comparingInt (MergedAnnotation ::getDistance ).reversed ();
213
+
146
214
147
215
/**
148
216
* Get the annotated {@link Field}.
149
217
*/
218
+ @ Nullable
150
219
public final Field getField () {
151
220
return this .field ;
152
221
}
@@ -243,20 +312,25 @@ public boolean equals(Object other) {
243
312
!Objects .equals (this .strategy , that .strategy )) {
244
313
return false ;
245
314
}
315
+
316
+ // by-name lookup
246
317
if (this .beanName != null ) {
247
318
return true ;
248
319
}
249
320
321
+ // TODO Validate equals() logic for null fields.
322
+
250
323
// by-type lookup
251
- return (Objects .equals (this .field .getName (), that .field .getName ()) &&
324
+ return (this .field != null && that .field != null &&
325
+ Objects .equals (this .field .getName (), that .field .getName ()) &&
252
326
this .fieldAnnotations .equals (that .fieldAnnotations ));
253
327
}
254
328
255
329
@ Override
256
330
public int hashCode () {
257
331
int hash = Objects .hash (getClass (), this .beanType .getType (), this .beanName , this .strategy );
258
- return (this .beanName != null ? hash : hash +
259
- Objects .hash (this .field . getName (), this .fieldAnnotations ));
332
+ return (this .beanName != null ? hash :
333
+ hash + Objects .hash (( this .field != null ? this . field . getName () : null ), this .fieldAnnotations ));
260
334
}
261
335
262
336
@ Override
@@ -269,7 +343,10 @@ public String toString() {
269
343
.toString ();
270
344
}
271
345
272
- private static Set <Annotation > annotationSet (Field field ) {
346
+ private static Set <Annotation > annotationSet (@ Nullable Field field ) {
347
+ if (field == null ) {
348
+ return Collections .emptySet ();
349
+ }
273
350
Annotation [] annotations = field .getAnnotations ();
274
351
return (annotations .length != 0 ? new HashSet <>(Arrays .asList (annotations )) : Collections .emptySet ());
275
352
}
0 commit comments