Skip to content

Commit 1f93777

Browse files
committed
Support ApplicationContextInitializers in the TCF
Starting with Spring 3.1 applications can specify contextInitializerClasses via context-param and init-param in web.xml; however, there is currently no way to have such initializers invoked in integration testing scenarios without writing a custom SmartContextLoader. For comprehensive integration testing it should therefore be possible to re-use ApplicationContextInitializers in the Spring TestContext Framework as well. This commit makes this possible at the @ContextConfiguration level by allowing an array of ACI types to be specified, and the out-of-the-box SmartContextLoader implementations invoke the declared initializers at the appropriate time. - Added initializers and inheritInitializers attributes to @ContextConfiguration. - Introduced support for ApplicationContextInitializers in ContextConfigurationAttributes, MergedContextConfiguration, and ContextLoaderUtils. - MergedContextConfiguration stores context initializer classes as a Set and incorporates them into the implementations of hashCode() and equals() for proper context caching. - ApplicationContextInitializers are invoked in the new prepareContext(GenericApplicationContext, MergedContextConfiguration) method in AbstractGenericContextLoader, and ordering declared via the Ordered interface and @order annotation is honored. - Updated DelegatingSmartContextLoader to support initializers. Specifically, a test class may optionally declare neither XML configuration files nor annotated classes and instead declare only application context initializers. In such cases, an attempt will still be made to detect defaults, but their absence will not result an an exception. - Documented support for application context initializers in Javadoc and in the testing chapter of the reference manual. Issue: SPR-9011
1 parent 9c8c967 commit 1f93777

24 files changed

+1596
-261
lines changed

spring-test/.springBeans

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<beansProjectDescription>
33
<version>1</version>
4-
<pluginVersion><![CDATA[2.6.0.201104111100-PATCH]]></pluginVersion>
4+
<pluginVersion><![CDATA[3.0.0.201208090952-RELEASE]]></pluginVersion>
55
<configSuffixes>
66
<configSuffix><![CDATA[xml]]></configSuffix>
77
</configSuffixes>
88
<enableImports><![CDATA[false]]></enableImports>
99
<configs>
1010
<config>src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml</config>
11+
<config>src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml</config>
1112
</configs>
1213
<configSets>
1314
</configSets>

spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26-
import org.springframework.context.annotation.Bean;
27-
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.ApplicationContextInitializer;
27+
import org.springframework.context.ConfigurableApplicationContext;
2828

2929
/**
30-
* {@code ContextConfiguration} defines class-level metadata that is
30+
* {@code @ContextConfiguration} defines class-level metadata that is
3131
* used to determine how to load and configure an
3232
* {@link org.springframework.context.ApplicationContext ApplicationContext}
33-
* for test classes.
33+
* for integration tests.
3434
*
3535
* <h3>Supported Resource Types</h3>
3636
*
3737
* <p>Prior to Spring 3.1, only path-based resource locations were supported.
38-
* As of Spring 3.1, {@link #loader context loaders} may choose to support
38+
* As of Spring 3.1, {@linkplain #loader context loaders} may choose to support
3939
* either path-based or class-based resources (but not both). Consequently
4040
* {@code @ContextConfiguration} can be used to declare either path-based
4141
* resource locations (via the {@link #locations} or {@link #value}
@@ -47,16 +47,20 @@
4747
* <p>The term <em>annotated class</em> can refer to any of the following.
4848
*
4949
* <ul>
50-
* <li>A class annotated with {@link Configuration @Configuration}</li>
50+
* <li>A class annotated with
51+
* {@link org.springframework.context.annotation.Configuration @Configuration}</li>
5152
* <li>A component (i.e., a class annotated with
5253
* {@link org.springframework.stereotype.Component @Component},
5354
* {@link org.springframework.stereotype.Service @Service},
5455
* {@link org.springframework.stereotype.Repository @Repository}, etc.)</li>
5556
* <li>A JSR-330 compliant class that is annotated with {@code javax.inject} annotations</li>
56-
* <li>Any other class that contains {@link Bean @Bean}-methods</li>
57+
* <li>Any other class that contains
58+
* {@link org.springframework.context.annotation.Bean @Bean}-methods</li>
5759
* </ul>
5860
*
59-
* Consult the JavaDoc for {@link Configuration @Configuration} and {@link Bean @Bean}
61+
* Consult the Javadoc for
62+
* {@link org.springframework.context.annotation.Configuration @Configuration} and
63+
* {@link org.springframework.context.annotation.Bean @Bean}
6064
* for further information regarding the configuration and semantics of
6165
* <em>annotated classes</em>.
6266
*
@@ -66,8 +70,8 @@
6670
* @see SmartContextLoader
6771
* @see ContextConfigurationAttributes
6872
* @see MergedContextConfiguration
69-
* @see org.springframework.context.ApplicationContext
7073
* @see ActiveProfiles
74+
* @see org.springframework.context.ApplicationContext
7175
*/
7276
@Documented
7377
@Inherited
@@ -82,6 +86,7 @@
8286
* with {@link #locations} or {@link #classes}, but it may be used
8387
* instead of {@link #locations}.
8488
* @since 3.0
89+
* @see #inheritLocations
8590
*/
8691
String[] value() default {};
8792

@@ -111,6 +116,7 @@
111116
* {@link #value} or {@link #classes}, but it may be used instead of
112117
* {@link #value}.
113118
* @since 2.5
119+
* @see #inheritLocations
114120
*/
115121
String[] locations() default {};
116122

@@ -131,9 +137,31 @@
131137
* @since 3.1
132138
* @see org.springframework.context.annotation.Configuration
133139
* @see org.springframework.test.context.support.AnnotationConfigContextLoader
140+
* @see #inheritLocations
134141
*/
135142
Class<?>[] classes() default {};
136143

144+
/**
145+
* The application context <em>initializer classes</em> to use for initializing
146+
* a {@link ConfigurableApplicationContext}.
147+
*
148+
* <p>The concrete {@code ConfigurableApplicationContext} type supported by each
149+
* declared initializer must be compatible with the type of {@code ApplicationContext}
150+
* created by the {@link SmartContextLoader} in use.
151+
*
152+
* <p>{@code SmartContextLoader} implementations typically detect whether
153+
* Spring's {@link org.springframework.core.Ordered Ordered} interface has been
154+
* implemented or if the @{@link org.springframework.core.annotation.Order Order}
155+
* annotation is present and sort instances accordingly prior to invoking them.
156+
*
157+
* @since 3.2
158+
* @see org.springframework.context.ApplicationContextInitializer
159+
* @see org.springframework.context.ConfigurableApplicationContext
160+
* @see #inheritInitializers
161+
* @see #loader
162+
*/
163+
Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers() default {};
164+
137165
/**
138166
* Whether or not {@link #locations resource locations} or <em>annotated
139167
* classes</em> from test superclasses should be <em>inherited</em>.
@@ -194,7 +222,45 @@
194222
boolean inheritLocations() default true;
195223

196224
/**
197-
* The type of {@link ContextLoader} (or {@link SmartContextLoader}) to use
225+
* Whether or not {@linkplain #initializers context initializers} from test
226+
* superclasses should be <em>inherited</em>.
227+
*
228+
* <p>The default value is <code>true</code>. This means that an annotated
229+
* class will <em>inherit</em> the application context initializers defined
230+
* by test superclasses. Specifically, the initializers for a given test
231+
* class will be added to the set of initializers defined by test
232+
* superclasses. Thus, subclasses have the option of <em>extending</em> the
233+
* set of initializers.
234+
*
235+
* <p>If <code>inheritInitializers</code> is set to <code>false</code>, the
236+
* initializers for the annotated class will <em>shadow</em> and effectively
237+
* replace any initializers defined by superclasses.
238+
*
239+
* <p>In the following example, the
240+
* {@link org.springframework.context.ApplicationContext ApplicationContext}
241+
* for {@code ExtendedTest} will be initialized using
242+
* {@code BaseInitializer} <strong>and</strong> {@code ExtendedInitializer}.
243+
* Note, however, that the order in which the initializers are invoked
244+
* depends on whether they implement {@link org.springframework.core.Ordered
245+
* Ordered} or are annotated with {@link org.springframework.core.annotation.Order
246+
* &#064;Order}.
247+
* <pre class="code">
248+
* &#064;ContextConfiguration(initializers = BaseInitializer.class)
249+
* public class BaseTest {
250+
* // ...
251+
* }
252+
*
253+
* &#064;ContextConfiguration(initializers = ExtendedInitializer.class)
254+
* public class ExtendedTest extends BaseTest {
255+
* // ...
256+
* }
257+
* </pre>
258+
* @since 3.2
259+
*/
260+
boolean inheritInitializers() default true;
261+
262+
/**
263+
* The type of {@link SmartContextLoader} (or {@link ContextLoader}) to use
198264
* for loading an {@link org.springframework.context.ApplicationContext
199265
* ApplicationContext}.
200266
*

spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818

1919
import org.apache.commons.logging.Log;
2020
import org.apache.commons.logging.LogFactory;
21+
22+
import org.springframework.context.ApplicationContextInitializer;
23+
import org.springframework.context.ConfigurableApplicationContext;
2124
import org.springframework.core.style.ToStringCreator;
2225
import org.springframework.util.Assert;
2326
import org.springframework.util.ObjectUtils;
2427

2528
/**
26-
* <code>ContextConfigurationAttributes</code> encapsulates the context
29+
* {@code ContextConfigurationAttributes} encapsulates the context
2730
* configuration attributes declared on a test class via
2831
* {@link ContextConfiguration @ContextConfiguration}.
2932
*
@@ -47,6 +50,10 @@ public class ContextConfigurationAttributes {
4750

4851
private final Class<? extends ContextLoader> contextLoaderClass;
4952

53+
private final Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers;
54+
55+
private final boolean inheritInitializers;
56+
5057

5158
/**
5259
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
@@ -68,8 +75,7 @@ private static String[] resolveLocations(Class<?> declaringClass, ContextConfigu
6875
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
6976
logger.error(msg);
7077
throw new IllegalStateException(msg);
71-
}
72-
else if (!ObjectUtils.isEmpty(valueLocations)) {
78+
} else if (!ObjectUtils.isEmpty(valueLocations)) {
7379
locations = valueLocations;
7480
}
7581

@@ -79,31 +85,59 @@ else if (!ObjectUtils.isEmpty(valueLocations)) {
7985
/**
8086
* Construct a new {@link ContextConfigurationAttributes} instance for the
8187
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
82-
* the {@link Class test class} that declared it.
88+
* the {@linkplain Class test class} that declared it.
8389
* @param declaringClass the test class that declared {@code @ContextConfiguration}
8490
* @param contextConfiguration the annotation from which to retrieve the attributes
8591
*/
8692
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
8793
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
88-
contextConfiguration.inheritLocations(), contextConfiguration.loader());
94+
contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
95+
contextConfiguration.inheritInitializers(), contextConfiguration.loader());
8996
}
9097

9198
/**
9299
* Construct a new {@link ContextConfigurationAttributes} instance for the
93-
* {@link Class test class} that declared the
100+
* {@linkplain Class test class} that declared the
94101
* {@link ContextConfiguration @ContextConfiguration} annotation and its
95102
* corresponding attributes.
96103
*
97104
* @param declaringClass the test class that declared {@code @ContextConfiguration}
98105
* @param locations the resource locations declared via {@code @ContextConfiguration}
99106
* @param classes the annotated classes declared via {@code @ContextConfiguration}
100-
* @param inheritLocations the <code>inheritLocations</code> flag declared via {@code @ContextConfiguration}
107+
* @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
101108
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
102109
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
103-
* <code>null</code>, or if the {@code locations} and {@code classes} are both non-empty
110+
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
111+
* @deprecated as of Spring 3.2, use
112+
* {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, Class)}
113+
* instead
104114
*/
115+
@Deprecated
105116
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
106117
boolean inheritLocations, Class<? extends ContextLoader> contextLoaderClass) {
118+
this(declaringClass, locations, classes, inheritLocations, null, true, contextLoaderClass);
119+
}
120+
121+
/**
122+
* Construct a new {@link ContextConfigurationAttributes} instance for the
123+
* {@linkplain Class test class} that declared the
124+
* {@link ContextConfiguration @ContextConfiguration} annotation and its
125+
* corresponding attributes.
126+
*
127+
* @param declaringClass the test class that declared {@code @ContextConfiguration}
128+
* @param locations the resource locations declared via {@code @ContextConfiguration}
129+
* @param classes the annotated classes declared via {@code @ContextConfiguration}
130+
* @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
131+
* @param initializers the context initializers declared via {@code @ContextConfiguration}
132+
* @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
133+
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
134+
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
135+
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
136+
*/
137+
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
138+
boolean inheritLocations,
139+
Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers,
140+
boolean inheritInitializers, Class<? extends ContextLoader> contextLoaderClass) {
107141

108142
Assert.notNull(declaringClass, "declaringClass must not be null");
109143
Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
@@ -122,14 +156,16 @@ public ContextConfigurationAttributes(Class<?> declaringClass, String[] location
122156
this.locations = locations;
123157
this.classes = classes;
124158
this.inheritLocations = inheritLocations;
159+
this.initializers = initializers;
160+
this.inheritInitializers = inheritInitializers;
125161
this.contextLoaderClass = contextLoaderClass;
126162
}
127163

128164
/**
129-
* Get the {@link Class class} that declared the
165+
* Get the {@linkplain Class class} that declared the
130166
* {@link ContextConfiguration @ContextConfiguration} annotation.
131167
*
132-
* @return the declaring class; never <code>null</code>
168+
* @return the declaring class; never {@code null}
133169
*/
134170
public Class<?> getDeclaringClass() {
135171
return declaringClass;
@@ -143,7 +179,7 @@ public Class<?> getDeclaringClass() {
143179
* represent a <em>processed</em> value that does not match the original value
144180
* declared via {@link ContextConfiguration @ContextConfiguration}.
145181
*
146-
* @return the resource locations; potentially <code>null</code> or <em>empty</em>
182+
* @return the resource locations; potentially {@code null} or <em>empty</em>
147183
* @see ContextConfiguration#value
148184
* @see ContextConfiguration#locations
149185
* @see #setLocations(String[])
@@ -170,7 +206,7 @@ public void setLocations(String[] locations) {
170206
* represent a <em>processed</em> value that does not match the original value
171207
* declared via {@link ContextConfiguration @ContextConfiguration}.
172208
*
173-
* @return the annotated classes; potentially <code>null</code> or <em>empty</em>
209+
* @return the annotated classes; potentially {@code null} or <em>empty</em>
174210
* @see ContextConfiguration#classes
175211
* @see #setClasses(Class[])
176212
*/
@@ -192,7 +228,7 @@ public void setClasses(Class<?>[] classes) {
192228
* Determine if this {@code ContextConfigurationAttributes} instance has
193229
* path-based resource locations.
194230
*
195-
* @return <code>true</code> if the {@link #getLocations() locations} array is not empty
231+
* @return {@code true} if the {@link #getLocations() locations} array is not empty
196232
* @see #hasResources()
197233
* @see #hasClasses()
198234
*/
@@ -204,7 +240,7 @@ public boolean hasLocations() {
204240
* Determine if this {@code ContextConfigurationAttributes} instance has
205241
* class-based resources.
206242
*
207-
* @return <code>true</code> if the {@link #getClasses() classes} array is not empty
243+
* @return {@code true} if the {@link #getClasses() classes} array is not empty
208244
* @see #hasResources()
209245
* @see #hasLocations()
210246
*/
@@ -216,7 +252,7 @@ public boolean hasClasses() {
216252
* Determine if this {@code ContextConfigurationAttributes} instance has
217253
* either path-based resource locations or class-based resources.
218254
*
219-
* @return <code>true</code> if either the {@link #getLocations() locations}
255+
* @return {@code true} if either the {@link #getLocations() locations}
220256
* or the {@link #getClasses() classes} array is not empty
221257
* @see #hasLocations()
222258
* @see #hasClasses()
@@ -226,21 +262,43 @@ public boolean hasResources() {
226262
}
227263

228264
/**
229-
* Get the <code>inheritLocations</code> flag that was declared via
265+
* Get the {@code inheritLocations} flag that was declared via
230266
* {@link ContextConfiguration @ContextConfiguration}.
231267
*
232-
* @return the <code>inheritLocations</code> flag
268+
* @return the {@code inheritLocations} flag
233269
* @see ContextConfiguration#inheritLocations
234270
*/
235271
public boolean isInheritLocations() {
236272
return inheritLocations;
237273
}
238274

239275
/**
240-
* Get the <code>ContextLoader</code> class that was declared via
276+
* Get the {@code ApplicationContextInitializer} classes that were declared via
277+
* {@link ContextConfiguration @ContextConfiguration}.
278+
*
279+
* @return the {@code ApplicationContextInitializer} classes
280+
* @since 3.2
281+
*/
282+
public Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] getInitializers() {
283+
return initializers;
284+
}
285+
286+
/**
287+
* Get the {@code inheritInitializers} flag that was declared via
288+
* {@link ContextConfiguration @ContextConfiguration}.
289+
*
290+
* @return the {@code inheritInitializers} flag
291+
* @since 3.2
292+
*/
293+
public boolean isInheritInitializers() {
294+
return inheritInitializers;
295+
}
296+
297+
/**
298+
* Get the {@code ContextLoader} class that was declared via
241299
* {@link ContextConfiguration @ContextConfiguration}.
242300
*
243-
* @return the <code>ContextLoader</code> class
301+
* @return the {@code ContextLoader} class
244302
* @see ContextConfiguration#loader
245303
*/
246304
public Class<? extends ContextLoader> getContextLoaderClass() {
@@ -258,6 +316,8 @@ public String toString() {
258316
.append("locations", ObjectUtils.nullSafeToString(locations))//
259317
.append("classes", ObjectUtils.nullSafeToString(classes))//
260318
.append("inheritLocations", inheritLocations)//
319+
.append("initializers", ObjectUtils.nullSafeToString(initializers))//
320+
.append("inheritInitializers", inheritInitializers)//
261321
.append("contextLoaderClass", contextLoaderClass.getName())//
262322
.toString();
263323
}

0 commit comments

Comments
 (0)