Skip to content

Commit b308659

Browse files
committed
Merge from sbrannen/SPR-9955
* SPR-9955: Introduce context bootstrap strategy in the TCF
2 parents 196cdef + a281bdb commit b308659

37 files changed

+2194
-1265
lines changed

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

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,29 +50,23 @@
5050
/**
5151
* Alias for {@link #profiles}.
5252
*
53-
* <p>This attribute may <strong>not</strong> be used in conjunction
54-
* with {@link #profiles} or {@link #resolver}, but it may be used
55-
* <em>instead</em> of them.
53+
* <p>This attribute may <strong>not</strong> be used in conjunction with
54+
* {@link #profiles}, but it may be used <em>instead</em> of {@link #profiles}.
5655
*/
5756
String[] value() default {};
5857

5958
/**
6059
* The bean definition profiles to activate.
6160
*
62-
* <p>This attribute may <strong>not</strong> be used in conjunction
63-
* with {@link #value} or {@link #resolver}, but it may be used
64-
* <em>instead</em> of them.
61+
* <p>This attribute may <strong>not</strong> be used in conjunction with
62+
* {@link #value}, but it may be used <em>instead</em> of {@link #value}.
6563
*/
6664
String[] profiles() default {};
6765

6866
/**
6967
* The type of {@link ActiveProfilesResolver} to use for resolving the active
7068
* bean definition profiles programmatically.
7169
*
72-
* <p>This attribute may <strong>not</strong> be used in conjunction
73-
* with {@link #profiles} or {@link #value}, but it may be used <em>instead</em>
74-
* of them in order to resolve the active profiles programmatically.
75-
*
7670
* @since 4.0
7771
* @see ActiveProfilesResolver
7872
*/
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
/**
20+
* {@code BootstrapContext} encapsulates the context in which the <em>Spring
21+
* TestContext Framework</em> is bootstrapped.
22+
*
23+
* @author Sam Brannen
24+
* @since 4.1
25+
* @see BootstrapWith
26+
* @see TestContextBootstrapper
27+
*/
28+
public interface BootstrapContext {
29+
30+
/**
31+
* Get the {@link Class test class} for this bootstrap context.
32+
* @return the test class (never {@code null})
33+
*/
34+
Class<?> getTestClass();
35+
36+
/**
37+
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
38+
* interaction with the <em>context cache</em>.
39+
*/
40+
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
41+
42+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
21+
import org.springframework.util.ClassUtils;
22+
23+
import static org.springframework.beans.BeanUtils.*;
24+
import static org.springframework.core.annotation.AnnotationUtils.*;
25+
26+
/**
27+
* {@code BootstrapUtils} is a collection of utility methods to assist with
28+
* bootstrapping the <em>Spring TestContext Framework</em>.
29+
*
30+
* @author Sam Brannen
31+
* @since 4.1
32+
* @see BootstrapWith
33+
* @see BootstrapContext
34+
* @see TestContextBootstrapper
35+
*/
36+
abstract class BootstrapUtils {
37+
38+
private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper";
39+
40+
private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
41+
42+
43+
private BootstrapUtils() {
44+
/* no-op */
45+
}
46+
47+
/**
48+
* Resolve the {@link TestContextBootstrapper} type for the test class in the
49+
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference
50+
* to the {@link BootstrapContext}.
51+
*
52+
* <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on
53+
* the test class, either directly or as a meta-annotation, then its
54+
* {@link BootstrapWith#value value} will be used as the bootstrapper type.
55+
* Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
56+
* DefaultTestContextBootstrapper} will be used.
57+
*
58+
* @param bootstrapContext the bootstrap context to use
59+
* @return a fully configured {@code TestContextBootstrapper}
60+
*/
61+
@SuppressWarnings("unchecked")
62+
static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) {
63+
Class<?> testClass = bootstrapContext.getTestClass();
64+
65+
Class<? extends TestContextBootstrapper> clazz = null;
66+
try {
67+
BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class);
68+
if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) {
69+
clazz = bootstrapWith.value();
70+
}
71+
else {
72+
clazz = (Class<? extends TestContextBootstrapper>) ClassUtils.forName(
73+
DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader());
74+
}
75+
76+
if (logger.isDebugEnabled()) {
77+
logger.debug(String.format("Instantiating TestContextBootstrapper from class [%s]", clazz.getName()));
78+
}
79+
80+
TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class);
81+
testContextBootstrapper.setBootstrapContext(bootstrapContext);
82+
83+
return testContextBootstrapper;
84+
}
85+
catch (Throwable t) {
86+
throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz
87+
+ "]. Specify @BootstrapWith's 'value' attribute "
88+
+ "or make the default bootstrapper class available.", t);
89+
}
90+
}
91+
92+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* {@code @BootstrapWith} defines class-level metadata that is used to determine
28+
* how to bootstrap the <em>Spring TestContext Framework</em>.
29+
*
30+
* <p>This annotation may also be used as a <em>meta-annotation</em> to create
31+
* custom <em>composed annotations</em>.
32+
*
33+
* @author Sam Brannen
34+
* @since 4.1
35+
* @see BootstrapContext
36+
* @see TestContextBootstrapper
37+
*/
38+
@Documented
39+
@Inherited
40+
@Retention(RetentionPolicy.RUNTIME)
41+
@Target(ElementType.TYPE)
42+
public @interface BootstrapWith {
43+
44+
/**
45+
* The {@link TestContextBootstrapper} to use to bootstrap the <em>Spring
46+
* TestContext Framework</em>.
47+
*/
48+
Class<? extends TestContextBootstrapper> value() default TestContextBootstrapper.class;
49+
50+
}
Lines changed: 41 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,97 +16,61 @@
1616

1717
package org.springframework.test.context;
1818

19-
import org.apache.commons.logging.Log;
20-
import org.apache.commons.logging.LogFactory;
2119
import org.springframework.context.ApplicationContext;
22-
import org.springframework.util.Assert;
20+
import org.springframework.context.ConfigurableApplicationContext;
21+
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
2322

2423
/**
25-
* {@code CacheAwareContextLoaderDelegate} loads application contexts from
26-
* {@link MergedContextConfiguration} by delegating to the
27-
* {@link ContextLoader} configured in the {@code MergedContextConfiguration}
28-
* and interacting transparently with the {@link ContextCache} behind the scenes.
24+
* A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
25+
* #loadContext loading} and {@linkplain #closeContext closing} application
26+
* contexts, interacting transparently with a <em>context cache</em> behind
27+
* the scenes.
2928
*
30-
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not implement the
29+
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
3130
* {@link ContextLoader} or {@link SmartContextLoader} interface.
3231
*
3332
* @author Sam Brannen
3433
* @since 3.2.2
3534
*/
36-
public class CacheAwareContextLoaderDelegate {
37-
38-
private static final Log logger = LogFactory.getLog(CacheAwareContextLoaderDelegate.class);
39-
40-
private final ContextCache contextCache;
41-
42-
43-
CacheAwareContextLoaderDelegate(ContextCache contextCache) {
44-
Assert.notNull(contextCache, "ContextCache must not be null");
45-
this.contextCache = contextCache;
46-
}
35+
public interface CacheAwareContextLoaderDelegate {
4736

4837
/**
49-
* Load the {@code ApplicationContext} for the supplied merged context
50-
* configuration. Supports both the {@link SmartContextLoader} and
51-
* {@link ContextLoader} SPIs.
52-
* @throws Exception if an error occurs while loading the application context
53-
*/
54-
private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
55-
throws Exception {
56-
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
57-
Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
58-
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
59-
60-
ApplicationContext applicationContext;
61-
62-
if (contextLoader instanceof SmartContextLoader) {
63-
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
64-
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
65-
}
66-
else {
67-
String[] locations = mergedContextConfiguration.getLocations();
68-
Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
69-
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
70-
applicationContext = contextLoader.loadContext(locations);
71-
}
72-
73-
return applicationContext;
74-
}
75-
76-
/**
77-
* Load the {@link ApplicationContext application context} for the supplied
78-
* merged context configuration.
38+
* Load the {@linkplain ApplicationContext application context} for the supplied
39+
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
40+
* configured in the given {@code MergedContextConfiguration}.
41+
*
42+
* <p>If the context is present in the <em>context cache</em> it will simply
43+
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
7944
*
80-
* <p>If the context is present in the cache it will simply be returned;
81-
* otherwise, it will be loaded, stored in the cache, and returned.
45+
* @param mergedContextConfiguration the merged context configuration to use
46+
* to load the application context; never {@code null}
8247
* @return the application context
8348
* @throws IllegalStateException if an error occurs while retrieving or
8449
* loading the application context
8550
*/
86-
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
87-
synchronized (contextCache) {
88-
ApplicationContext context = contextCache.get(mergedContextConfiguration);
89-
if (context == null) {
90-
try {
91-
context = loadContextInternal(mergedContextConfiguration);
92-
if (logger.isDebugEnabled()) {
93-
logger.debug(String.format("Storing ApplicationContext in cache under key [%s].",
94-
mergedContextConfiguration));
95-
}
96-
contextCache.put(mergedContextConfiguration, context);
97-
}
98-
catch (Exception ex) {
99-
throw new IllegalStateException("Failed to load ApplicationContext", ex);
100-
}
101-
}
102-
else {
103-
if (logger.isDebugEnabled()) {
104-
logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].",
105-
mergedContextConfiguration));
106-
}
107-
}
108-
return context;
109-
}
110-
}
51+
ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration);
52+
53+
/**
54+
* Remove the {@linkplain ApplicationContext application context} for the
55+
* supplied {@link MergedContextConfiguration} from the <em>context cache</em>
56+
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is
57+
* an instance of {@link ConfigurableApplicationContext}.
58+
*
59+
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when
60+
* removing the context from the cache. See the Javadoc for {@link HierarchyMode}
61+
* for details.
62+
*
63+
* <p>Generally speaking, this method should only be called if the state of
64+
* a singleton bean has been changed (potentially affecting future interaction
65+
* with the context) or if the context needs to be prematurely removed from
66+
* the cache.
67+
*
68+
* @param mergedContextConfiguration the merged context configuration for the
69+
* application context to close; never {@code null}
70+
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
71+
* is not part of a hierarchy
72+
* @since 4.1
73+
*/
74+
void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode);
11175

11276
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
*
9292
* <p>This attribute may <strong>not</strong> be used in conjunction with
9393
* {@link #locations}, but it may be used instead of {@link #locations}.
94+
*
9495
* @since 3.0
9596
* @see #inheritLocations
9697
*/
@@ -120,6 +121,7 @@
120121
*
121122
* <p>This attribute may <strong>not</strong> be used in conjunction with
122123
* {@link #value}, but it may be used instead of {@link #value}.
124+
*
123125
* @since 2.5
124126
* @see #inheritLocations
125127
*/

0 commit comments

Comments
 (0)