3131import org .springframework .context .ApplicationContextInitializer ;
3232import org .springframework .context .ConfigurableApplicationContext ;
3333import org .springframework .core .annotation .AnnotationAttributes ;
34+ import org .springframework .core .annotation .AnnotationAwareOrderComparator ;
3435import org .springframework .core .annotation .AnnotationUtils ;
36+ import org .springframework .core .io .support .SpringFactoriesLoader ;
3537import org .springframework .test .context .BootstrapContext ;
3638import org .springframework .test .context .CacheAwareContextLoaderDelegate ;
3739import org .springframework .test .context .ContextConfiguration ;
5557 * provides most of the behavior required by a bootstrapper.
5658 *
5759 * <p>Concrete subclasses typically will only need to provide implementations for
58- * the following {@code abstract} methods:
60+ * the following methods:
5961 * <ul>
60- * <li>{@link #getDefaultTestExecutionListenerClassNames}
6162 * <li>{@link #getDefaultContextLoaderClass}
62- * <li>{@link #buildMergedContextConfiguration }
63+ * <li>{@link #processMergedContextConfiguration }
6364 * </ul>
6465 *
6566 * @author Sam Brannen
@@ -98,16 +99,18 @@ public final List<TestExecutionListener> getTestExecutionListeners() {
9899 Class <?> clazz = getBootstrapContext ().getTestClass ();
99100 Class <TestExecutionListeners > annotationType = TestExecutionListeners .class ;
100101 List <Class <? extends TestExecutionListener >> classesList = new ArrayList <Class <? extends TestExecutionListener >>();
102+ boolean usingDefaults = false ;
101103
102104 AnnotationDescriptor <TestExecutionListeners > descriptor = MetaAnnotationUtils .findAnnotationDescriptor (clazz ,
103105 annotationType );
104106
105107 // Use defaults?
106108 if (descriptor == null ) {
107109 if (logger .isDebugEnabled ()) {
108- logger .debug ("@TestExecutionListeners is not present for class [" + clazz . getName ()
109- + "]: using defaults." );
110+ logger .debug (String . format ( "@TestExecutionListeners is not present for class [%s]: using defaults." ,
111+ clazz . getName ()) );
110112 }
113+ usingDefaults = true ;
111114 classesList .addAll (getDefaultTestExecutionListenerClasses ());
112115 }
113116 else {
@@ -142,6 +145,20 @@ else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
142145 }
143146 }
144147
148+ List <TestExecutionListener > listeners = instantiateListeners (classesList );
149+
150+ // Sort by Ordered/@Order if we loaded default listeners.
151+ if (usingDefaults ) {
152+ AnnotationAwareOrderComparator .sort (listeners );
153+ }
154+
155+ if (logger .isInfoEnabled ()) {
156+ logger .info (String .format ("Using TestExecutionListeners: %s" , listeners ));
157+ }
158+ return listeners ;
159+ }
160+
161+ private List <TestExecutionListener > instantiateListeners (List <Class <? extends TestExecutionListener >> classesList ) {
145162 List <TestExecutionListener > listeners = new ArrayList <TestExecutionListener >(classesList .size ());
146163 for (Class <? extends TestExecutionListener > listenerClass : classesList ) {
147164 NoClassDefFoundError ncdfe = null ;
@@ -194,6 +211,28 @@ protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionLis
194211 return defaultListenerClasses ;
195212 }
196213
214+ /**
215+ * Get the names of the default {@link TestExecutionListener} classes for
216+ * this bootstrapper.
217+ * <p>The default implementation looks up all
218+ * {@code org.springframework.test.context.TestExecutionListener} entries
219+ * configured in all {@code META-INF/spring.factories} files on the classpath.
220+ * <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
221+ * @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
222+ * classes
223+ * @see SpringFactoriesLoader#loadFactoryNames
224+ */
225+ protected List <String > getDefaultTestExecutionListenerClassNames () {
226+ final List <String > classNames = SpringFactoriesLoader .loadFactoryNames (TestExecutionListener .class ,
227+ getClass ().getClassLoader ());
228+
229+ if (logger .isInfoEnabled ()) {
230+ logger .info (String .format ("Loaded default TestExecutionListener class names from location [%s]: %s" ,
231+ SpringFactoriesLoader .FACTORIES_RESOURCE_LOCATION , classNames ));
232+ }
233+ return Collections .unmodifiableList (classNames );
234+ }
235+
197236 /**
198237 * {@inheritDoc}
199238 */
@@ -302,9 +341,11 @@ private MergedContextConfiguration buildMergedContextConfiguration(Class<?> test
302341 String [] activeProfiles = ActiveProfilesUtils .resolveActiveProfiles (testClass );
303342 MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils .buildMergedTestPropertySources (testClass );
304343
305- return buildMergedContextConfiguration (testClass , locations , classes , initializerClasses , activeProfiles ,
306- mergedTestPropertySources .getLocations (), mergedTestPropertySources .getProperties (), contextLoader ,
307- cacheAwareContextLoaderDelegate , parentConfig );
344+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration (testClass , locations , classes ,
345+ initializerClasses , activeProfiles , mergedTestPropertySources .getLocations (),
346+ mergedTestPropertySources .getProperties (), contextLoader , cacheAwareContextLoaderDelegate , parentConfig );
347+
348+ return processMergedContextConfiguration (mergedConfig );
308349 }
309350
310351 /**
@@ -383,15 +424,6 @@ private Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
383424 return null ;
384425 }
385426
386- /**
387- * Get the names of the default {@link TestExecutionListener} classes for
388- * this bootstrapper.
389- * <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
390- * @return an <em>unmodifiable</em> list of names of default {@code
391- * TestExecutionListener} classes
392- */
393- protected abstract List <String > getDefaultTestExecutionListenerClassNames ();
394-
395427 /**
396428 * Determine the default {@link ContextLoader} class to use for the supplied
397429 * test class.
@@ -403,34 +435,19 @@ private Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
403435 protected abstract Class <? extends ContextLoader > getDefaultContextLoaderClass (Class <?> testClass );
404436
405437 /**
406- * Build a {@link MergedContextConfiguration} instance from the supplied,
407- * merged values.
408- * <p>Concrete subclasses typically will only need to instantiate
409- * {@link MergedContextConfiguration} (or a specialized subclass thereof)
410- * from the provided values; further processing and merging of values is likely
411- * unnecessary.
412- * @param testClass the test class for which the {@code MergedContextConfiguration}
413- * should be built (must not be {@code null})
414- * @param locations the merged resource locations
415- * @param classes the merged annotated classes
416- * @param initializerClasses the merged context initializer classes
417- * @param activeProfiles the merged active bean definition profiles
418- * @param propertySourceLocations the merged {@code PropertySource} locations
419- * @param propertySourceProperties the merged {@code PropertySource} properties
420- * @param contextLoader the resolved {@code ContextLoader}
421- * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate
422- * to be provided to the instantiated {@code MergedContextConfiguration}
423- * @param parentConfig the merged context configuration for the parent application
424- * context in a context hierarchy, or {@code null} if there is no parent
425- * @return the fully initialized {@code MergedContextConfiguration}
438+ * Process the supplied, newly instantiated {@link MergedContextConfiguration} instance.
439+ * <p>The returned {@link MergedContextConfiguration} instance may be a wrapper
440+ * around or a replacement for the original.
441+ * <p>The default implementation simply returns the supplied instance unmodified.
442+ * <p>Concrete subclasses may choose to return a specialized subclass of
443+ * {@link MergedContextConfiguration} based on properties in the supplied instance.
444+ * @param mergedConfig the {@code MergedContextConfiguration} to process;
445+ * never {@code null}
446+ * @return a fully initialized {@code MergedContextConfiguration}; never
447+ * {@code null}
426448 */
427- protected abstract MergedContextConfiguration buildMergedContextConfiguration (
428- Class <?> testClass ,
429- String [] locations ,
430- Class <?>[] classes ,
431- Set <Class <? extends ApplicationContextInitializer <? extends ConfigurableApplicationContext >>> initializerClasses ,
432- String [] activeProfiles , String [] propertySourceLocations , String [] propertySourceProperties ,
433- ContextLoader contextLoader , CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate ,
434- MergedContextConfiguration parentConfig );
449+ protected MergedContextConfiguration processMergedContextConfiguration (MergedContextConfiguration mergedConfig ) {
450+ return mergedConfig ;
451+ }
435452
436453}
0 commit comments