24
24
import java .util .List ;
25
25
import java .util .Map ;
26
26
import java .util .Set ;
27
+ import java .util .stream .Collectors ;
27
28
28
29
import org .apache .commons .logging .Log ;
29
30
import org .apache .commons .logging .LogFactory ;
32
33
import org .springframework .beans .BeanUtils ;
33
34
import org .springframework .core .annotation .AnnotationAwareOrderComparator ;
34
35
import org .springframework .core .io .support .SpringFactoriesLoader ;
36
+ import org .springframework .core .io .support .SpringFactoriesLoader .FailureHandler ;
35
37
import org .springframework .lang .Nullable ;
36
38
import org .springframework .test .context .BootstrapContext ;
37
39
import org .springframework .test .context .CacheAwareContextLoaderDelegate ;
@@ -112,7 +114,7 @@ public TestContext buildTestContext() {
112
114
public final List <TestExecutionListener > getTestExecutionListeners () {
113
115
Class <?> clazz = getBootstrapContext ().getTestClass ();
114
116
Class <TestExecutionListeners > annotationType = TestExecutionListeners .class ;
115
- List <Class <? extends TestExecutionListener >> classesList = new ArrayList <>();
117
+ List <TestExecutionListener > listeners = new ArrayList <>(8 );
116
118
boolean usingDefaults = false ;
117
119
118
120
AnnotationDescriptor <TestExecutionListeners > descriptor =
@@ -125,7 +127,7 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
125
127
clazz .getName ()));
126
128
}
127
129
usingDefaults = true ;
128
- classesList .addAll (getDefaultTestExecutionListenerClasses ());
130
+ listeners .addAll (getDefaultTestExecutionListeners ());
129
131
}
130
132
else {
131
133
// Traverse the class hierarchy...
@@ -149,24 +151,27 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
149
151
"@TestExecutionListeners for class [%s]." , descriptor .getRootDeclaringClass ().getName ()));
150
152
}
151
153
usingDefaults = true ;
152
- classesList .addAll (getDefaultTestExecutionListenerClasses ());
154
+ listeners .addAll (getDefaultTestExecutionListeners ());
153
155
}
154
156
155
- classesList .addAll (0 , Arrays . asList (testExecutionListeners .listeners ()));
157
+ listeners .addAll (0 , instantiateListeners (testExecutionListeners .listeners ()));
156
158
157
159
descriptor = (inheritListeners ? parentDescriptor : null );
158
160
}
159
161
}
160
162
161
- Collection <Class <? extends TestExecutionListener >> classesToUse = classesList ;
162
- // Remove possible duplicates if we loaded default listeners.
163
163
if (usingDefaults ) {
164
- classesToUse = new LinkedHashSet <>(classesList );
165
- }
164
+ // Remove possible duplicates if we loaded default listeners.
165
+ List <TestExecutionListener > uniqueListeners = new ArrayList <>(listeners .size ());
166
+ listeners .forEach (listener -> {
167
+ Class <? extends TestExecutionListener > listenerClass = listener .getClass ();
168
+ if (uniqueListeners .stream ().map (Object ::getClass ).noneMatch (listenerClass ::equals )) {
169
+ uniqueListeners .add (listener );
170
+ }
171
+ });
172
+ listeners = uniqueListeners ;
166
173
167
- List <TestExecutionListener > listeners = instantiateListeners (classesToUse );
168
- // Sort by Ordered/@Order if we loaded default listeners.
169
- if (usingDefaults ) {
174
+ // Sort by Ordered/@Order if we loaded default listeners.
170
175
AnnotationAwareOrderComparator .sort (listeners );
171
176
}
172
177
@@ -176,8 +181,61 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
176
181
return listeners ;
177
182
}
178
183
179
- private List <TestExecutionListener > instantiateListeners (Collection <Class <? extends TestExecutionListener >> classes ) {
180
- List <TestExecutionListener > listeners = new ArrayList <>(classes .size ());
184
+ /**
185
+ * Get the default {@link TestExecutionListener TestExecutionListeners} for
186
+ * this bootstrapper.
187
+ * <p>This method is invoked by {@link #getTestExecutionListeners()}.
188
+ * <p>The default implementation looks up and instantiates all
189
+ * {@code org.springframework.test.context.TestExecutionListener} entries
190
+ * configured in all {@code META-INF/spring.factories} files on the classpath.
191
+ * <p>If a particular listener cannot be loaded due to a {@link LinkageError}
192
+ * or {@link ClassNotFoundException}, a {@code DEBUG} message will be logged,
193
+ * but the associated exception will not be rethrown. A {@link RuntimeException}
194
+ * or any other {@link Error} will be rethrown. Any other exception will be
195
+ * thrown wrapped in an {@link IllegalStateException}.
196
+ * @return an <em>unmodifiable</em> list of default {@code TestExecutionListener}
197
+ * instances
198
+ * @since 6.0
199
+ * @see SpringFactoriesLoader#forDefaultResourceLocation()
200
+ * @see SpringFactoriesLoader#load(Class, FailureHandler)
201
+ */
202
+ protected List <TestExecutionListener > getDefaultTestExecutionListeners () {
203
+ FailureHandler failureHandler = (factoryType , factoryImplementationName , ex ) -> {
204
+ if (ex instanceof LinkageError || ex instanceof ClassNotFoundException ) {
205
+ if (logger .isDebugEnabled ()) {
206
+ logger .debug ("Could not load default TestExecutionListener [" + factoryImplementationName +
207
+ "]. Specify custom listener classes or make the default listener classes available." , ex );
208
+ }
209
+ }
210
+ else {
211
+ if (ex instanceof RuntimeException runtimeException ) {
212
+ throw runtimeException ;
213
+ }
214
+ if (ex instanceof Error error ) {
215
+ throw error ;
216
+ }
217
+ throw new IllegalStateException ("Failed to load default TestExecutionListener [" +
218
+ factoryImplementationName + "]." , ex );
219
+ }
220
+ };
221
+
222
+ List <TestExecutionListener > listeners = SpringFactoriesLoader .forDefaultResourceLocation ()
223
+ .load (TestExecutionListener .class , failureHandler );
224
+
225
+ if (logger .isInfoEnabled ()) {
226
+ List <String > classNames = listeners .stream ()
227
+ .map (listener -> listener .getClass ().getName ())
228
+ .collect (Collectors .toList ());
229
+ logger .info (String .format ("Loaded default TestExecutionListener implementations from location [%s]: %s" ,
230
+ SpringFactoriesLoader .FACTORIES_RESOURCE_LOCATION , classNames ));
231
+ }
232
+
233
+ return Collections .unmodifiableList (listeners );
234
+ }
235
+
236
+ @ SuppressWarnings ("unchecked" )
237
+ private List <TestExecutionListener > instantiateListeners (Class <? extends TestExecutionListener >... classes ) {
238
+ List <TestExecutionListener > listeners = new ArrayList <>(classes .length );
181
239
for (Class <? extends TestExecutionListener > listenerClass : classes ) {
182
240
try {
183
241
listeners .add (BeanUtils .instantiateClass (listenerClass ));
@@ -201,53 +259,6 @@ private List<TestExecutionListener> instantiateListeners(Collection<Class<? exte
201
259
return listeners ;
202
260
}
203
261
204
- /**
205
- * Get the default {@link TestExecutionListener} classes for this bootstrapper.
206
- * <p>This method is invoked by {@link #getTestExecutionListeners()} and
207
- * delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
208
- * retrieve the class names.
209
- * <p>If a particular class cannot be loaded, a {@code DEBUG} message will
210
- * be logged, but the associated exception will not be rethrown.
211
- */
212
- @ SuppressWarnings ("unchecked" )
213
- protected Set <Class <? extends TestExecutionListener >> getDefaultTestExecutionListenerClasses () {
214
- Set <Class <? extends TestExecutionListener >> defaultListenerClasses = new LinkedHashSet <>();
215
- ClassLoader cl = getClass ().getClassLoader ();
216
- for (String className : getDefaultTestExecutionListenerClassNames ()) {
217
- try {
218
- defaultListenerClasses .add ((Class <? extends TestExecutionListener >) ClassUtils .forName (className , cl ));
219
- }
220
- catch (Throwable ex ) {
221
- if (logger .isDebugEnabled ()) {
222
- logger .debug ("Could not load default TestExecutionListener class [" + className +
223
- "]. Specify custom listener classes or make the default listener classes available." , ex );
224
- }
225
- }
226
- }
227
- return defaultListenerClasses ;
228
- }
229
-
230
- /**
231
- * Get the names of the default {@link TestExecutionListener} classes for
232
- * this bootstrapper.
233
- * <p>The default implementation looks up all
234
- * {@code org.springframework.test.context.TestExecutionListener} entries
235
- * configured in all {@code META-INF/spring.factories} files on the classpath.
236
- * <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
237
- * @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
238
- * classes
239
- * @see SpringFactoriesLoader#loadFactoryNames
240
- */
241
- protected List <String > getDefaultTestExecutionListenerClassNames () {
242
- List <String > classNames =
243
- SpringFactoriesLoader .loadFactoryNames (TestExecutionListener .class , getClass ().getClassLoader ());
244
- if (logger .isInfoEnabled ()) {
245
- logger .info (String .format ("Loaded default TestExecutionListener class names from location [%s]: %s" ,
246
- SpringFactoriesLoader .FACTORIES_RESOURCE_LOCATION , classNames ));
247
- }
248
- return Collections .unmodifiableList (classNames );
249
- }
250
-
251
262
/**
252
263
* {@inheritDoc}
253
264
*/
0 commit comments