16
16
17
17
package org .springframework .test .context .cache ;
18
18
19
+ import java .lang .reflect .InvocationTargetException ;
20
+ import java .util .Collection ;
21
+ import java .util .List ;
22
+
19
23
import org .apache .commons .logging .Log ;
20
24
import org .apache .commons .logging .LogFactory ;
21
25
22
26
import org .springframework .context .ApplicationContext ;
23
27
import org .springframework .context .ApplicationContextInitializer ;
24
28
import org .springframework .context .ConfigurableApplicationContext ;
25
29
import org .springframework .context .support .GenericApplicationContext ;
30
+ import org .springframework .core .io .support .SpringFactoriesLoader ;
26
31
import org .springframework .lang .Nullable ;
27
32
import org .springframework .test .annotation .DirtiesContext .HierarchyMode ;
28
33
import org .springframework .test .context .ApplicationContextFailureProcessor ;
44
49
* invoke the {@link #DefaultCacheAwareContextLoaderDelegate(ContextCache)}
45
50
* and provide a custom {@link ContextCache} implementation.
46
51
*
52
+ * <p>As of Spring Framework 6.0, this class loads {@link ApplicationContextFailureProcessor}
53
+ * implementations via the {@link SpringFactoriesLoader} mechanism and delegates to
54
+ * them in {@link #loadContext(MergedContextConfiguration)} to process context
55
+ * load failures.
56
+ *
47
57
* @author Sam Brannen
48
58
* @since 4.1
49
59
*/
50
60
public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
51
61
52
62
private static final Log logger = LogFactory .getLog (DefaultCacheAwareContextLoaderDelegate .class );
53
63
64
+ private static List <ApplicationContextFailureProcessor > contextFailureProcessors =
65
+ getApplicationContextFailureProcessors ();
66
+
54
67
/**
55
68
* Default static cache of Spring application contexts.
56
69
*/
@@ -60,9 +73,6 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
60
73
61
74
private final ContextCache contextCache ;
62
75
63
- @ Nullable
64
- private ApplicationContextFailureProcessor contextFailureProcessor ;
65
-
66
76
67
77
/**
68
78
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
@@ -117,14 +127,14 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedContextCo
117
127
Throwable cause = ex ;
118
128
if (ex instanceof ContextLoadException cle ) {
119
129
cause = cle .getCause ();
120
- if ( this . contextFailureProcessor != null ) {
130
+ for ( ApplicationContextFailureProcessor contextFailureProcessor : contextFailureProcessors ) {
121
131
try {
122
- this . contextFailureProcessor .processLoadFailure (cle .getApplicationContext (), cause );
132
+ contextFailureProcessor .processLoadFailure (cle .getApplicationContext (), cause );
123
133
}
124
134
catch (Throwable throwable ) {
125
135
if (logger .isDebugEnabled ()) {
126
136
logger .debug ("Ignoring exception thrown from ApplicationContextFailureProcessor [%s]: %s"
127
- .formatted (this . contextFailureProcessor , throwable ));
137
+ .formatted (contextFailureProcessor , throwable ));
128
138
}
129
139
}
130
140
}
@@ -153,12 +163,6 @@ public void closeContext(MergedContextConfiguration mergedContextConfiguration,
153
163
}
154
164
}
155
165
156
- @ Override
157
- public void setContextFailureProcessor (ApplicationContextFailureProcessor contextFailureProcessor ) {
158
- this .contextFailureProcessor = contextFailureProcessor ;
159
- }
160
-
161
-
162
166
/**
163
167
* Get the {@link ContextCache} used by this context loader delegate.
164
168
*/
@@ -236,6 +240,7 @@ private ContextLoader getContextLoader(MergedContextConfiguration mergedConfig)
236
240
* unmodified.
237
241
* <p>This allows for transparent {@link org.springframework.test.context.cache.ContextCache ContextCache}
238
242
* support for AOT-optimized application contexts.
243
+ * @since 6.0
239
244
*/
240
245
@ SuppressWarnings ("unchecked" )
241
246
private MergedContextConfiguration replaceIfNecessary (MergedContextConfiguration mergedConfig ) {
@@ -248,4 +253,71 @@ private MergedContextConfiguration replaceIfNecessary(MergedContextConfiguration
248
253
return mergedConfig ;
249
254
}
250
255
256
+ /**
257
+ * Get the {@link ApplicationContextFailureProcessor} implementations to use,
258
+ * loaded via the {@link SpringFactoriesLoader} mechanism.
259
+ * @return the context failure processors to use
260
+ * @since 6.0
261
+ */
262
+ private static List <ApplicationContextFailureProcessor > getApplicationContextFailureProcessors () {
263
+ SpringFactoriesLoader loader = SpringFactoriesLoader .forDefaultResourceLocation (
264
+ DefaultCacheAwareContextLoaderDelegate .class .getClassLoader ());
265
+ List <ApplicationContextFailureProcessor > processors = loader .load (ApplicationContextFailureProcessor .class ,
266
+ DefaultCacheAwareContextLoaderDelegate ::handleInstantiationFailure );
267
+ if (logger .isTraceEnabled ()) {
268
+ logger .trace ("Loaded default ApplicationContextFailureProcessor implementations from location [%s]: %s"
269
+ .formatted (SpringFactoriesLoader .FACTORIES_RESOURCE_LOCATION , classNames (processors )));
270
+ }
271
+ else if (logger .isDebugEnabled ()) {
272
+ logger .debug ("Loaded default ApplicationContextFailureProcessor implementations from location [%s]: %s"
273
+ .formatted (SpringFactoriesLoader .FACTORIES_RESOURCE_LOCATION , classSimpleNames (processors )));
274
+ }
275
+ return processors ;
276
+ }
277
+
278
+ private static void handleInstantiationFailure (
279
+ Class <?> factoryType , String factoryImplementationName , Throwable failure ) {
280
+
281
+ Throwable ex = (failure instanceof InvocationTargetException ite ?
282
+ ite .getTargetException () : failure );
283
+ if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError ) {
284
+ logSkippedComponent (factoryType , factoryImplementationName , ex );
285
+ }
286
+ else if (ex instanceof LinkageError ) {
287
+ if (logger .isDebugEnabled ()) {
288
+ logger .debug ("""
289
+ Could not load %1$s [%2$s]. Specify custom %1$s classes or make the default %1$s classes \
290
+ available.""" .formatted (factoryType .getSimpleName (), factoryImplementationName ), ex );
291
+ }
292
+ }
293
+ else {
294
+ if (ex instanceof RuntimeException runtimeException ) {
295
+ throw runtimeException ;
296
+ }
297
+ if (ex instanceof Error error ) {
298
+ throw error ;
299
+ }
300
+ throw new IllegalStateException (
301
+ "Failed to load %s [%s]." .formatted (factoryType .getSimpleName (), factoryImplementationName ), ex );
302
+ }
303
+ }
304
+
305
+ private static void logSkippedComponent (Class <?> factoryType , String factoryImplementationName , Throwable ex ) {
306
+ if (logger .isDebugEnabled ()) {
307
+ logger .debug ("""
308
+ Skipping candidate %1$s [%2$s] due to a missing dependency. \
309
+ Specify custom %1$s classes or make the default %1$s classes \
310
+ and their required dependencies available. Offending class: [%3$s]"""
311
+ .formatted (factoryType .getSimpleName (), factoryImplementationName , ex .getMessage ()));
312
+ }
313
+ }
314
+
315
+ private static List <String > classNames (Collection <?> components ) {
316
+ return components .stream ().map (Object ::getClass ).map (Class ::getName ).toList ();
317
+ }
318
+
319
+ private static List <String > classSimpleNames (Collection <?> components ) {
320
+ return components .stream ().map (Object ::getClass ).map (Class ::getSimpleName ).toList ();
321
+ }
322
+
251
323
}
0 commit comments