diff --git a/spring-test/.springBeans b/spring-test/.springBeans index 0d97a70a64..7d64f21cfb 100644 --- a/spring-test/.springBeans +++ b/spring-test/.springBeans @@ -1,13 +1,14 @@ 1 - + src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml + src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java index 60bfa10728..4710ee7af9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java @@ -23,19 +23,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; /** - * {@code ContextConfiguration} defines class-level metadata that is + * {@code @ContextConfiguration} defines class-level metadata that is * used to determine how to load and configure an * {@link org.springframework.context.ApplicationContext ApplicationContext} - * for test classes. + * for integration tests. * *

Supported Resource Types

* *

Prior to Spring 3.1, only path-based resource locations were supported. - * As of Spring 3.1, {@link #loader context loaders} may choose to support + * As of Spring 3.1, {@linkplain #loader context loaders} may choose to support * either path-based or class-based resources (but not both). Consequently * {@code @ContextConfiguration} can be used to declare either path-based * resource locations (via the {@link #locations} or {@link #value} @@ -47,16 +47,20 @@ import org.springframework.context.annotation.Configuration; *

The term annotated class can refer to any of the following. * *

* - * Consult the JavaDoc for {@link Configuration @Configuration} and {@link Bean @Bean} + * Consult the Javadoc for + * {@link org.springframework.context.annotation.Configuration @Configuration} and + * {@link org.springframework.context.annotation.Bean @Bean} * for further information regarding the configuration and semantics of * annotated classes. * @@ -66,8 +70,8 @@ import org.springframework.context.annotation.Configuration; * @see SmartContextLoader * @see ContextConfigurationAttributes * @see MergedContextConfiguration - * @see org.springframework.context.ApplicationContext * @see ActiveProfiles + * @see org.springframework.context.ApplicationContext */ @Documented @Inherited @@ -82,6 +86,7 @@ public @interface ContextConfiguration { * with {@link #locations} or {@link #classes}, but it may be used * instead of {@link #locations}. * @since 3.0 + * @see #inheritLocations */ String[] value() default {}; @@ -111,6 +116,7 @@ public @interface ContextConfiguration { * {@link #value} or {@link #classes}, but it may be used instead of * {@link #value}. * @since 2.5 + * @see #inheritLocations */ String[] locations() default {}; @@ -131,9 +137,31 @@ public @interface ContextConfiguration { * @since 3.1 * @see org.springframework.context.annotation.Configuration * @see org.springframework.test.context.support.AnnotationConfigContextLoader + * @see #inheritLocations */ Class[] classes() default {}; + /** + * The application context initializer classes to use for initializing + * a {@link ConfigurableApplicationContext}. + * + *

The concrete {@code ConfigurableApplicationContext} type supported by each + * declared initializer must be compatible with the type of {@code ApplicationContext} + * created by the {@link SmartContextLoader} in use. + * + *

{@code SmartContextLoader} implementations typically detect whether + * Spring's {@link org.springframework.core.Ordered Ordered} interface has been + * implemented or if the @{@link org.springframework.core.annotation.Order Order} + * annotation is present and sort instances accordingly prior to invoking them. + * + * @since 3.2 + * @see org.springframework.context.ApplicationContextInitializer + * @see org.springframework.context.ConfigurableApplicationContext + * @see #inheritInitializers + * @see #loader + */ + Class>[] initializers() default {}; + /** * Whether or not {@link #locations resource locations} or annotated * classes from test superclasses should be inherited. @@ -194,7 +222,45 @@ public @interface ContextConfiguration { boolean inheritLocations() default true; /** - * The type of {@link ContextLoader} (or {@link SmartContextLoader}) to use + * Whether or not {@linkplain #initializers context initializers} from test + * superclasses should be inherited. + * + *

The default value is true. This means that an annotated + * class will inherit the application context initializers defined + * by test superclasses. Specifically, the initializers for a given test + * class will be added to the set of initializers defined by test + * superclasses. Thus, subclasses have the option of extending the + * set of initializers. + * + *

If inheritInitializers is set to false, the + * initializers for the annotated class will shadow and effectively + * replace any initializers defined by superclasses. + * + *

In the following example, the + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * for {@code ExtendedTest} will be initialized using + * {@code BaseInitializer} and {@code ExtendedInitializer}. + * Note, however, that the order in which the initializers are invoked + * depends on whether they implement {@link org.springframework.core.Ordered + * Ordered} or are annotated with {@link org.springframework.core.annotation.Order + * @Order}. + *

+	 * @ContextConfiguration(initializers = BaseInitializer.class)
+	 * public class BaseTest {
+	 *     // ...
+	 * }
+	 * 
+	 * @ContextConfiguration(initializers = ExtendedInitializer.class)
+	 * public class ExtendedTest extends BaseTest {
+	 *     // ...
+	 * }
+	 * 
+ * @since 3.2 + */ + boolean inheritInitializers() default true; + + /** + * The type of {@link SmartContextLoader} (or {@link ContextLoader}) to use * for loading an {@link org.springframework.context.ApplicationContext * ApplicationContext}. * diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java index b7983030ce..5a481c190d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java @@ -18,12 +18,15 @@ package org.springframework.test.context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * ContextConfigurationAttributes encapsulates the context + * {@code ContextConfigurationAttributes} encapsulates the context * configuration attributes declared on a test class via * {@link ContextConfiguration @ContextConfiguration}. * @@ -47,6 +50,10 @@ public class ContextConfigurationAttributes { private final Class contextLoaderClass; + private final Class>[] initializers; + + private final boolean inheritInitializers; + /** * Resolve resource locations from the {@link ContextConfiguration#locations() locations} @@ -68,8 +75,7 @@ public class ContextConfigurationAttributes { ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations)); logger.error(msg); throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueLocations)) { + } else if (!ObjectUtils.isEmpty(valueLocations)) { locations = valueLocations; } @@ -79,31 +85,59 @@ public class ContextConfigurationAttributes { /** * Construct a new {@link ContextConfigurationAttributes} instance for the * supplied {@link ContextConfiguration @ContextConfiguration} annotation and - * the {@link Class test class} that declared it. + * the {@linkplain Class test class} that declared it. * @param declaringClass the test class that declared {@code @ContextConfiguration} * @param contextConfiguration the annotation from which to retrieve the attributes */ public ContextConfigurationAttributes(Class declaringClass, ContextConfiguration contextConfiguration) { this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(), - contextConfiguration.inheritLocations(), contextConfiguration.loader()); + contextConfiguration.inheritLocations(), contextConfiguration.initializers(), + contextConfiguration.inheritInitializers(), contextConfiguration.loader()); } /** * Construct a new {@link ContextConfigurationAttributes} instance for the - * {@link Class test class} that declared the + * {@linkplain Class test class} that declared the * {@link ContextConfiguration @ContextConfiguration} annotation and its * corresponding attributes. * * @param declaringClass the test class that declared {@code @ContextConfiguration} * @param locations the resource locations declared via {@code @ContextConfiguration} * @param classes the annotated classes declared via {@code @ContextConfiguration} - * @param inheritLocations the inheritLocations flag declared via {@code @ContextConfiguration} + * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration} * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration} * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is - * null, or if the {@code locations} and {@code classes} are both non-empty + * {@code null}, or if the {@code locations} and {@code classes} are both non-empty + * @deprecated as of Spring 3.2, use + * {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, Class)} + * instead */ + @Deprecated public ContextConfigurationAttributes(Class declaringClass, String[] locations, Class[] classes, boolean inheritLocations, Class contextLoaderClass) { + this(declaringClass, locations, classes, inheritLocations, null, true, contextLoaderClass); + } + + /** + * Construct a new {@link ContextConfigurationAttributes} instance for the + * {@linkplain Class test class} that declared the + * {@link ContextConfiguration @ContextConfiguration} annotation and its + * corresponding attributes. + * + * @param declaringClass the test class that declared {@code @ContextConfiguration} + * @param locations the resource locations declared via {@code @ContextConfiguration} + * @param classes the annotated classes declared via {@code @ContextConfiguration} + * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration} + * @param initializers the context initializers declared via {@code @ContextConfiguration} + * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration} + * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration} + * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is + * {@code null}, or if the {@code locations} and {@code classes} are both non-empty + */ + public ContextConfigurationAttributes(Class declaringClass, String[] locations, Class[] classes, + boolean inheritLocations, + Class>[] initializers, + boolean inheritInitializers, Class contextLoaderClass) { Assert.notNull(declaringClass, "declaringClass must not be null"); Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null"); @@ -122,14 +156,16 @@ public class ContextConfigurationAttributes { this.locations = locations; this.classes = classes; this.inheritLocations = inheritLocations; + this.initializers = initializers; + this.inheritInitializers = inheritInitializers; this.contextLoaderClass = contextLoaderClass; } /** - * Get the {@link Class class} that declared the + * Get the {@linkplain Class class} that declared the * {@link ContextConfiguration @ContextConfiguration} annotation. * - * @return the declaring class; never null + * @return the declaring class; never {@code null} */ public Class getDeclaringClass() { return declaringClass; @@ -143,7 +179,7 @@ public class ContextConfigurationAttributes { * represent a processed value that does not match the original value * declared via {@link ContextConfiguration @ContextConfiguration}. * - * @return the resource locations; potentially null or empty + * @return the resource locations; potentially {@code null} or empty * @see ContextConfiguration#value * @see ContextConfiguration#locations * @see #setLocations(String[]) @@ -170,7 +206,7 @@ public class ContextConfigurationAttributes { * represent a processed value that does not match the original value * declared via {@link ContextConfiguration @ContextConfiguration}. * - * @return the annotated classes; potentially null or empty + * @return the annotated classes; potentially {@code null} or empty * @see ContextConfiguration#classes * @see #setClasses(Class[]) */ @@ -192,7 +228,7 @@ public class ContextConfigurationAttributes { * Determine if this {@code ContextConfigurationAttributes} instance has * path-based resource locations. * - * @return true if the {@link #getLocations() locations} array is not empty + * @return {@code true} if the {@link #getLocations() locations} array is not empty * @see #hasResources() * @see #hasClasses() */ @@ -204,7 +240,7 @@ public class ContextConfigurationAttributes { * Determine if this {@code ContextConfigurationAttributes} instance has * class-based resources. * - * @return true if the {@link #getClasses() classes} array is not empty + * @return {@code true} if the {@link #getClasses() classes} array is not empty * @see #hasResources() * @see #hasLocations() */ @@ -216,7 +252,7 @@ public class ContextConfigurationAttributes { * Determine if this {@code ContextConfigurationAttributes} instance has * either path-based resource locations or class-based resources. * - * @return true if either the {@link #getLocations() locations} + * @return {@code true} if either the {@link #getLocations() locations} * or the {@link #getClasses() classes} array is not empty * @see #hasLocations() * @see #hasClasses() @@ -226,10 +262,10 @@ public class ContextConfigurationAttributes { } /** - * Get the inheritLocations flag that was declared via + * Get the {@code inheritLocations} flag that was declared via * {@link ContextConfiguration @ContextConfiguration}. * - * @return the inheritLocations flag + * @return the {@code inheritLocations} flag * @see ContextConfiguration#inheritLocations */ public boolean isInheritLocations() { @@ -237,10 +273,32 @@ public class ContextConfigurationAttributes { } /** - * Get the ContextLoader class that was declared via + * Get the {@code ApplicationContextInitializer} classes that were declared via * {@link ContextConfiguration @ContextConfiguration}. * - * @return the ContextLoader class + * @return the {@code ApplicationContextInitializer} classes + * @since 3.2 + */ + public Class>[] getInitializers() { + return initializers; + } + + /** + * Get the {@code inheritInitializers} flag that was declared via + * {@link ContextConfiguration @ContextConfiguration}. + * + * @return the {@code inheritInitializers} flag + * @since 3.2 + */ + public boolean isInheritInitializers() { + return inheritInitializers; + } + + /** + * Get the {@code ContextLoader} class that was declared via + * {@link ContextConfiguration @ContextConfiguration}. + * + * @return the {@code ContextLoader} class * @see ContextConfiguration#loader */ public Class getContextLoaderClass() { @@ -258,6 +316,8 @@ public class ContextConfigurationAttributes { .append("locations", ObjectUtils.nullSafeToString(locations))// .append("classes", ObjectUtils.nullSafeToString(classes))// .append("inheritLocations", inheritLocations)// + .append("initializers", ObjectUtils.nullSafeToString(initializers))// + .append("inheritInitializers", inheritInitializers)// .append("contextLoaderClass", contextLoaderClass.getName())// .toString(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java index 12fdf6142a..f28364a9b4 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java @@ -23,8 +23,8 @@ import org.springframework.context.ApplicationContext; * for an integration test managed by the Spring TestContext Framework. * *

Note: as of Spring 3.1, implement {@link SmartContextLoader} instead - * of this interface in order to provide support for annotated classes and active - * bean definition profiles. + * of this interface in order to provide support for annotated classes, active + * bean definition profiles, and application context initializers. * *

Clients of a ContextLoader should call * {@link #processLocations(Class,String...) processLocations()} prior to diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index 8f62aecbe5..a5ad3516f0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -21,12 +21,15 @@ import static org.springframework.core.annotation.AnnotationUtils.findAnnotation import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -156,8 +159,7 @@ abstract class ContextLoaderUtils { } return (Class) ContextLoaderUtils.class.getClassLoader().loadClass( defaultContextLoaderClassName); - } - catch (ClassNotFoundException ex) { + } catch (ClassNotFoundException ex) { throw new IllegalStateException("Could not load default ContextLoader class [" + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' " + "attribute or make the default loader class available."); @@ -169,17 +171,15 @@ abstract class ContextLoaderUtils { * attributes} for the supplied {@link Class class} and its superclasses. * *

Note that the {@link ContextConfiguration#inheritLocations - * inheritLocations} flag of {@link ContextConfiguration - * @ContextConfiguration} will be taken into consideration. - * Specifically, if the inheritLocations flag is set to - * true, configuration attributes defined in the test - * class will be appended to the configuration attributes defined in - * superclasses. + * inheritLocations} and {@link ContextConfiguration#inheritInitializers() + * inheritInitializers} flags of {@link ContextConfiguration + * @ContextConfiguration} will not be taken into + * consideration. If these flags need to be honored, that must be handled + * manually when traversing the list returned by this method. * * @param clazz the class for which to resolve the configuration attributes (must * not be null) - * @return the list of configuration attributes for the specified class, - * including configuration attributes from superclasses if appropriate + * @return the list of configuration attributes for the specified class * (never null) * @throws IllegalArgumentException if the supplied class is null or * if {@code @ContextConfiguration} is not present on the supplied class @@ -211,13 +211,69 @@ abstract class ContextLoaderUtils { attributesList.add(0, attributes); - declaringClass = contextConfiguration.inheritLocations() ? findAnnotationDeclaringClass(annotationType, - declaringClass.getSuperclass()) : null; + declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass()); } return attributesList; } + /** + * Create a copy of the supplied list of {@code ContextConfigurationAttributes} + * in reverse order. + * + * @since 3.2 + */ + private static List reverseContextConfigurationAttributes( + List configAttributesList) { + List configAttributesListReversed = new ArrayList( + configAttributesList); + Collections.reverse(configAttributesListReversed); + return configAttributesListReversed; + } + + /** + * Resolve the list of merged {@code ApplicationContextInitializer} classes + * for the supplied list of {@code ContextConfigurationAttributes}. + * + *

Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers} + * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into + * consideration. Specifically, if the inheritInitializers flag is + * set to true for a given level in the class hierarchy represented by + * the provided configuration attributes, context initializer classes defined + * at the given level will be merged with those defined in higher levels + * of the class hierarchy. + * + * @param configAttributesList the list of configuration attributes to process + * (must not be null) + * @return the list of merged context initializer classes, including those + * from superclasses if appropriate (never null) + * @since 3.2 + */ + static Set>> resolveInitializerClasses( + List configAttributesList) { + Assert.notNull(configAttributesList, "configAttributesList must not be null"); + + final Set>> initializerClasses = // + new HashSet>>(); + + // Traverse config attributes in reverse order (i.e., as if we were traversing up + // the class hierarchy). + for (ContextConfigurationAttributes configAttributes : reverseContextConfigurationAttributes(configAttributesList)) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Processing context initializers for context configuration attributes %s", + configAttributes)); + } + + initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers())); + + if (!configAttributes.isInheritInitializers()) { + break; + } + } + + return initializerClasses; + } + /** * Resolve active bean definition profiles for the supplied {@link Class}. * @@ -266,8 +322,7 @@ abstract class ContextLoaderUtils { ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); logger.error(msg); throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueProfiles)) { + } else if (!ObjectUtils.isEmpty(valueProfiles)) { profiles = valueProfiles; } @@ -309,31 +364,38 @@ abstract class ContextLoaderUtils { final List locationsList = new ArrayList(); final List> classesList = new ArrayList>(); - for (ContextConfigurationAttributes configAttributes : configAttributesList) { + // Traverse config attributes in reverse order (i.e., as if we were traversing up + // the class hierarchy). + for (ContextConfigurationAttributes configAttributes : reverseContextConfigurationAttributes(configAttributesList)) { if (logger.isTraceEnabled()) { - logger.trace(String.format( - "Processing locations and classes for context configuration attributes [%s]", configAttributes)); + logger.trace(String.format("Processing locations and classes for context configuration attributes %s", + configAttributes)); } if (contextLoader instanceof SmartContextLoader) { SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; smartContextLoader.processContextConfiguration(configAttributes); - locationsList.addAll(Arrays.asList(configAttributes.getLocations())); - classesList.addAll(Arrays.asList(configAttributes.getClasses())); - } - else { + locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); + classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); + } else { String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), configAttributes.getLocations()); - locationsList.addAll(Arrays.asList(processedLocations)); + locationsList.addAll(0, Arrays.asList(processedLocations)); // Legacy ContextLoaders don't know how to process classes } + + if (!configAttributes.isInheritLocations()) { + break; + } } String[] locations = StringUtils.toStringArray(locationsList); Class[] classes = ClassUtils.toClassArray(classesList); + Set>> initializerClasses = resolveInitializerClasses(configAttributesList); String[] activeProfiles = resolveActiveProfiles(testClass); - return new MergedContextConfiguration(testClass, locations, classes, activeProfiles, contextLoader); + return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, + contextLoader); } } 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 274d5da8c4..e484253b8c 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 @@ -18,9 +18,13 @@ package org.springframework.test.context; import java.io.Serializable; import java.util.Arrays; +import java.util.Collections; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -59,10 +63,13 @@ public class MergedContextConfiguration implements Serializable { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + private static final Set>> EMPTY_INITIALIZER_CLASSES = // + Collections.>> emptySet(); private final Class testClass; private final String[] locations; private final Class[] classes; + private final Set>> contextInitializerClasses; private final String[] activeProfiles; private final ContextLoader contextLoader; @@ -75,6 +82,12 @@ public class MergedContextConfiguration implements Serializable { return classes == null ? EMPTY_CLASS_ARRAY : classes; } + private static Set>> processContextInitializerClasses( + Set>> contextInitializerClasses) { + return contextInitializerClasses == null ? EMPTY_INITIALIZER_CLASSES + : Collections.unmodifiableSet(contextInitializerClasses); + } + private static String[] processActiveProfiles(String[] activeProfiles) { if (activeProfiles == null) { return EMPTY_STRING_ARRAY; @@ -111,46 +124,84 @@ public class MergedContextConfiguration implements Serializable { * @param classes the merged annotated classes * @param activeProfiles the merged active bean definition profiles * @param contextLoader the resolved ContextLoader + * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader) */ public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, String[] activeProfiles, ContextLoader contextLoader) { + this(testClass, locations, classes, null, activeProfiles, contextLoader); + } + + /** + * Create a new {@code MergedContextConfiguration} instance for the + * supplied test class, resource locations, annotated classes, context + * initializers, active profiles, and {@code ContextLoader}. + * + *

If a null value is supplied for locations, + * classes, or activeProfiles an empty array will + * be stored instead. If a null value is supplied for the + * contextInitializerClasses an empty set will be stored instead. + * Furthermore, active profiles will be sorted, and duplicate profiles will + * be removed. + * + * @param testClass the test class for which the configuration was merged + * @param locations the merged resource locations + * @param classes the merged annotated classes + * @param contextInitializerClasses the merged context initializer classes + * @param activeProfiles the merged active bean definition profiles + * @param contextLoader the resolved ContextLoader + */ + public MergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> contextInitializerClasses, + String[] activeProfiles, ContextLoader contextLoader) { this.testClass = testClass; this.locations = processLocations(locations); this.classes = processClasses(classes); + this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses); this.activeProfiles = processActiveProfiles(activeProfiles); this.contextLoader = contextLoader; } /** - * Get the {@link Class test class} associated with this {@code MergedContextConfiguration}. + * Get the {@linkplain Class test class} associated with this {@code MergedContextConfiguration}. */ public Class getTestClass() { return testClass; } /** - * Get the merged resource locations for the {@link #getTestClass() test class}. + * Get the merged resource locations for the {@linkplain #getTestClass() test class}. */ public String[] getLocations() { return locations; } /** - * Get the merged annotated classes for the {@link #getTestClass() test class}. + * Get the merged annotated classes for the {@linkplain #getTestClass() test class}. */ public Class[] getClasses() { return classes; } /** - * Get the merged active bean definition profiles for the {@link #getTestClass() test class}. + * Get the merged {@code ApplicationContextInitializer} classes for the + * {@linkplain #getTestClass() test class}. + */ + public Set>> getContextInitializerClasses() { + return contextInitializerClasses; + } + + /** + * Get the merged active bean definition profiles for the {@linkplain #getTestClass() test class}. */ public String[] getActiveProfiles() { return activeProfiles; } /** - * Get the resolved {@link ContextLoader} for the {@link #getTestClass() test class}. + * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. */ public ContextLoader getContextLoader() { return contextLoader; @@ -159,7 +210,7 @@ public class MergedContextConfiguration implements Serializable { /** * Generate a unique hash code for all properties of this * {@code MergedContextConfiguration} excluding the - * {@link #getTestClass() test class}. + * {@linkplain #getTestClass() test class}. */ @Override public int hashCode() { @@ -167,6 +218,7 @@ public class MergedContextConfiguration implements Serializable { int result = 1; result = prime * result + Arrays.hashCode(locations); result = prime * result + Arrays.hashCode(classes); + result = prime * result + contextInitializerClasses.hashCode(); result = prime * result + Arrays.hashCode(activeProfiles); result = prime * result + nullSafeToString(contextLoader).hashCode(); return result; @@ -174,10 +226,11 @@ public class MergedContextConfiguration implements Serializable { /** * Determine if the supplied object is equal to this {@code MergedContextConfiguration} - * instance by comparing both object's {@link #getLocations() locations}, - * {@link #getClasses() annotated classes}, {@link #getActiveProfiles() - * active profiles}, and the fully qualified names of their - * {@link #getContextLoader() ContextLoaders}. + * instance by comparing both object's {@linkplain #getLocations() locations}, + * {@linkplain #getClasses() annotated classes}, + * {@linkplain #getContextInitializerClasses() context initializer classes}, + * {@linkplain #getActiveProfiles() active profiles}, and the fully qualified + * names of their {@link #getContextLoader() ContextLoaders}. */ @Override public boolean equals(Object obj) { @@ -197,6 +250,9 @@ public class MergedContextConfiguration implements Serializable { if (!Arrays.equals(this.classes, that.classes)) { return false; } + if (!this.contextInitializerClasses.equals(that.contextInitializerClasses)) { + return false; + } if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) { return false; } @@ -208,9 +264,10 @@ public class MergedContextConfiguration implements Serializable { } /** - * Provide a String representation of the {@link #getTestClass() test class}, - * {@link #getLocations() locations}, {@link #getClasses() annotated classes}, - * {@link #getActiveProfiles() active profiles}, and the name of the + * Provide a String representation of the {@linkplain #getTestClass() test class}, + * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, + * {@linkplain #getContextInitializerClasses() context initializer classes}, + * {@linkplain #getActiveProfiles() active profiles}, and the name of the * {@link #getContextLoader() ContextLoader}. */ @Override @@ -219,6 +276,7 @@ public class MergedContextConfiguration implements Serializable { .append("testClass", testClass)// .append("locations", ObjectUtils.nullSafeToString(locations))// .append("classes", ObjectUtils.nullSafeToString(classes))// + .append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))// .append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))// .append("contextLoader", nullSafeToString(contextLoader))// .toString(); diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index e1669ac176..06145c6e94 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -16,14 +16,25 @@ package org.springframework.test.context.support; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -66,11 +77,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * *