From e6d16148e576fafe094c2bc1ef975c25576eeb7b Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 14 Aug 2014 19:28:50 +0200 Subject: [PATCH] Support automatic discovery of default TELs Prior to this commit, there was no declarative mechanism for a custom TestExecutionListener to be registered as a default TestExecutionListener. This commit introduces support for discovering default TestExecutionListener implementations via the SpringFactoriesLoader mechanism. Specifically, the spring-test module declares all core default TestExecutionListeners under the org.springframework.test.context.TestExecutionListener key in its META-INF/spring.factories properties file, and third-party frameworks and developers can contribute to the list of default TestExecutionListeners in the same manner. - AbstractTestContextBootstrapper uses the SpringFactoriesLoader to look up the class names of all registered default TestExecutionListeners and sorts the instantiated listeners using AnnotationAwareOrderComparator. - DefaultTestContextBootstrapper and WebTestContextBootstrapper now rely on the SpringFactoriesLoader mechanism for finding default TestExecutionListeners instead of hard coding fully qualified class names. - To ensure that default TestExecutionListeners are registered in the correct order, each can implement Ordered or declare @Order. - AbstractTestExecutionListener and all default TestExecutionListeners provided by Spring now implement Ordered with appropriate values. - Introduced "copy constructors" in MergedContextConfiguration and WebMergedContextConfiguration - SpringFactoriesLoader now uses AnnotationAwareOrderComparator instead of OrderComparator. Issue: SPR-11466 --- .../io/support/SpringFactoriesLoader.java | 9 +- .../context/MergedContextConfiguration.java | 13 +++ .../test/context/TestContextBootstrapper.java | 12 +- .../test/context/TestExecutionListener.java | 8 +- .../jdbc/SqlScriptsTestExecutionListener.java | 8 ++ .../AbstractTestContextBootstrapper.java | 107 ++++++++++-------- .../AbstractTestExecutionListener.java | 17 ++- .../DefaultTestContextBootstrapper.java | 61 +--------- ...endencyInjectionTestExecutionListener.java | 13 ++- .../DirtiesContextTestExecutionListener.java | 36 +++--- .../TransactionalTestExecutionListener.java | 8 ++ .../web/ServletTestExecutionListener.java | 8 ++ .../web/WebMergedContextConfiguration.java | 16 ++- .../web/WebTestContextBootstrapper.java | 57 ++-------- .../main/resources/META-INF/spring.factories | 8 ++ .../context/TestExecutionListenersTests.java | 2 +- .../jdbc/TransactionalSqlScriptsTests.java | 25 +++- .../test/resources/META-INF/spring.factories | 2 + 18 files changed, 227 insertions(+), 183 deletions(-) create mode 100644 spring-test/src/main/resources/META-INF/spring.factories create mode 100644 spring-test/src/test/resources/META-INF/spring.factories diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java index 9125716580d..d6eeabf7e90 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java @@ -27,7 +27,7 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.UrlResource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -48,12 +48,13 @@ import org.springframework.util.StringUtils; * * @author Arjen Poutsma * @author Juergen Hoeller + * @author Sam Brannen * @since 3.2 */ public abstract class SpringFactoriesLoader { /** The location to look for the factories. Can be present in multiple JAR files. */ - private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; + public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); @@ -61,7 +62,7 @@ public abstract class SpringFactoriesLoader { /** * Load the factory implementations of the given type from the default location, * using the given class loader. - *

The returned factories are ordered in accordance with the {@link OrderComparator}. + *

The returned factories are ordered in accordance with the {@link AnnotationAwareOrderComparator}. * @param factoryClass the interface or abstract class representing the factory * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) */ @@ -79,7 +80,7 @@ public abstract class SpringFactoriesLoader { for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } - OrderComparator.sort(result); + AnnotationAwareOrderComparator.sort(result); return result; } diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index b3dbceb10c4..6e1386fe33a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -79,6 +79,7 @@ public class MergedContextConfiguration implements Serializable { private final String[] propertySourceProperties; private final ContextLoader contextLoader; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; + private final MergedContextConfiguration parent; @@ -185,6 +186,18 @@ public class MergedContextConfiguration implements Serializable { cacheAwareContextLoaderDelegate, parent); } + /** + * Create a new {@code MergedContextConfiguration} instance by copying + * all fields from the supplied {@code MergedContextConfiguration}. + * @since 4.1 + */ + public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { + this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes, + mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations, + mergedConfig.propertySourceProperties, mergedConfig.contextLoader, + mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); + } + /** * Create a new {@code MergedContextConfiguration} instance for the * supplied parameters. diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java index 72c1be0acaa..a61bb7cf009 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java @@ -63,8 +63,16 @@ public interface TestContextBootstrapper { * for the test class in the {@link BootstrapContext} associated with this bootstrapper. *

If {@link TestExecutionListeners @TestExecutionListeners} is not * present on the test class in the {@code BootstrapContext}, - * default listeners should be returned. Concrete implementations - * are free to determine what comprises the set of default listeners. + * default listeners should be returned. Furthermore, default + * listeners must be sorted using + * {@link org.springframework.core.annotation.AnnotationAwareOrderComparator + * AnnotationAwareOrderComparator}. + *

Concrete implementations are free to determine what comprises the + * set of default listeners. However, by default, the Spring TestContext + * Framework will use the + * {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader} + * mechanism to look up all {@code TestExecutionListener} class names + * configured in all {@code META-INF/spring.factories} files on the classpath. *

The {@link TestExecutionListeners#inheritListeners() inheritListeners} * flag of {@link TestExecutionListeners @TestExecutionListeners} must be * taken into consideration. Specifically, if the {@code inheritListeners} diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java index 74b9ee5f6c4..8eb58591d5a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java @@ -23,7 +23,13 @@ package org.springframework.test.context; *

Concrete implementations must provide a {@code public} no-args constructor, * so that listeners can be instantiated transparently by tools and configuration * mechanisms. - *

Spring provides the following out-of-the-box implementations: + *

Implementations may optionally declare the position in which they should + * be ordered among the chain of default listeners via the + * {@link org.springframework.core.Ordered Order} interface or + * {@link org.springframework.core.annotation.Order @Order} annotation. See + * {@link TestContextBootstrapper#getTestExecutionListeners()} for details. + *

Spring provides the following out-of-the-box implementations (all of + * which are annotated with {@code @Order}): *