Skip to content

Commit 129488c

Browse files
committed
Improve extensibility of TestContext bootstrapping & context caching
- DefaultBootstrapContext and DefaultCacheAwareContextLoaderDelegate are now public classes in the 'support' subpackage. - Introduced getCacheAwareContextLoaderDelegate() in AbstractTestContextBootstrapper as an extension point for configuring custom ContextCache support. - Introduced reflection-based createBootstrapContext() utility method in BootstrapUtils; TestContextManager now delegates to BootstrapUtils in order to avoid package cycles. - Introduced logStatistics() method in the ContextCache API and defined statistics logging category as a constant. - DefaultCacheAwareContextLoaderDelegate now delegates to ContextCache.logStatistics(). Issue: SPR-12683
1 parent e6c24f7 commit 129488c

16 files changed

+184
-55
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public interface BootstrapContext {
3535

3636
/**
3737
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
38-
* interaction with the <em>context cache</em>.
38+
* interaction with the {@code ContextCache}.
39+
* @return the context loader delegate (never {@code null})
3940
*/
4041
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
4142

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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,6 +16,8 @@
1616

1717
package org.springframework.test.context;
1818

19+
import java.lang.reflect.Constructor;
20+
1921
import org.apache.commons.logging.Log;
2022
import org.apache.commons.logging.LogFactory;
2123

@@ -36,6 +38,10 @@
3638
*/
3739
abstract class BootstrapUtils {
3840

41+
private static final String DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME = "org.springframework.test.context.support.DefaultBootstrapContext";
42+
43+
private static final String DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME = "org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate";
44+
3945
private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper";
4046

4147
private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
@@ -45,6 +51,55 @@ private BootstrapUtils() {
4551
/* no-op */
4652
}
4753

54+
/**
55+
* Create the {@code BootstrapContext} for the specified {@linkplain Class test class}.
56+
*
57+
* <p>Uses reflection to create a {@link org.springframework.test.context.support.DefaultBootstrapContext}
58+
* that uses a {@link org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate}.
59+
*
60+
* @param testClass the test class for which the bootstrap context should be created
61+
* @return a new {@code BootstrapContext}; never {@code null}
62+
*/
63+
@SuppressWarnings("unchecked")
64+
static BootstrapContext createBootstrapContext(Class<?> testClass) {
65+
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = createCacheAwareContextLoaderDelegate();
66+
67+
Class<? extends BootstrapContext> clazz = null;
68+
try {
69+
clazz = (Class<? extends BootstrapContext>) ClassUtils.forName(DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME,
70+
BootstrapUtils.class.getClassLoader());
71+
72+
Constructor<? extends BootstrapContext> constructor = clazz.getConstructor(Class.class,
73+
CacheAwareContextLoaderDelegate.class);
74+
75+
if (logger.isDebugEnabled()) {
76+
logger.debug(String.format("Instantiating BootstrapContext using constructor [%s]", constructor));
77+
}
78+
return instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate);
79+
}
80+
catch (Throwable t) {
81+
throw new IllegalStateException("Could not load BootstrapContext [" + clazz + "]", t);
82+
}
83+
}
84+
85+
@SuppressWarnings("unchecked")
86+
private static CacheAwareContextLoaderDelegate createCacheAwareContextLoaderDelegate() {
87+
Class<? extends CacheAwareContextLoaderDelegate> clazz = null;
88+
try {
89+
clazz = (Class<? extends CacheAwareContextLoaderDelegate>) ClassUtils.forName(
90+
DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME, BootstrapUtils.class.getClassLoader());
91+
92+
if (logger.isDebugEnabled()) {
93+
logger.debug(String.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]",
94+
clazz.getName()));
95+
}
96+
return instantiateClass(clazz, CacheAwareContextLoaderDelegate.class);
97+
}
98+
catch (Throwable t) {
99+
throw new IllegalStateException("Could not load CacheAwareContextLoaderDelegate [" + clazz + "]", t);
100+
}
101+
}
102+
48103
/**
49104
* Resolve the {@link TestContextBootstrapper} type for the test class in the
50105
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public interface CacheAwareContextLoaderDelegate {
4040
* configured in the given {@code MergedContextConfiguration}.
4141
* <p>If the context is present in the {@code ContextCache} it will simply
4242
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
43+
* <p>The cache statistics should be logged by invoking
44+
* {@link ContextCache#logStatistics()}.
4345
* @param mergedContextConfiguration the merged context configuration to use
4446
* to load the application context; never {@code null}
4547
* @return the application context

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@
4141
*/
4242
public interface ContextCache {
4343

44+
/**
45+
* The name of the logging category used for reporting {@code ContextCache}
46+
* statistics.
47+
*/
48+
public static final String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache";
49+
50+
4451
/**
4552
* Determine whether there is a cached context for the given key.
4653
* @param key the context key (never {@code null})
@@ -126,19 +133,18 @@ public interface ContextCache {
126133
void clearStatistics();
127134

128135
/**
129-
* Generate a text string containing the implementation type of this
130-
* cache and its statistics.
131-
* <p>The value returned by this method will be used primarily for
132-
* logging purposes.
133-
* <p>Specifically, the returned string should contain the name of the
134-
* concrete {@code ContextCache} implementation, the {@linkplain #size},
135-
* {@linkplain #getParentContextCount() parent context count},
136-
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount()
137-
* miss count}, and any other information useful in monitoring the
138-
* state of this cache.
139-
* @return a string representation of this cache, including statistics
136+
* Log the statistics for this {@code ContextCache} at {@code DEBUG} level
137+
* using the {@value #CONTEXT_CACHE_LOGGING_CATEGORY} logging category.
138+
* <p>The following information should be logged.
139+
* <ul>
140+
* <li>name of the concrete {@code ContextCache} implementation</li>
141+
* <li>{@linkplain #size}</li>
142+
* <li>{@linkplain #getParentContextCount() parent context count}</li>
143+
* <li>{@linkplain #getHitCount() hit count}</li>
144+
* <li>{@linkplain #getMissCount() miss count}</li>
145+
* <li>any other information useful for monitoring the state of this cache</li>
146+
* </ul>
140147
*/
141-
@Override
142-
abstract String toString();
148+
void logStatistics();
143149

144150
}

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,22 +102,23 @@ public class TestContextManager {
102102
*/
103103
public TestContextManager(Class<?> testClass) {
104104
BootstrapContext bootstrapContext = createBootstrapContext(testClass);
105-
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
106-
this.testContext = testContextBootstrapper.buildTestContext();
107-
registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
105+
TestContextBootstrapper bootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
106+
this.testContext = bootstrapper.buildTestContext();
107+
registerTestExecutionListeners(bootstrapper.getTestExecutionListeners());
108108
}
109109

110110
/**
111111
* Create the {@code BootstrapContext} for the specified {@linkplain Class test class}.
112-
* <p>The default implementation creates a {@link DefaultBootstrapContext} that
113-
* uses the {@link DefaultCacheAwareContextLoaderDelegate}.
112+
* <p>The default implementation creates a
113+
* {@link org.springframework.test.context.support.DefaultBootstrapContext DefaultBootstrapContext}
114+
* that uses a
115+
* {@link org.springframework.test.context.support.DefaultCacheAwareContextLoaderDelegate DefaultCacheAwareContextLoaderDelegate}.
114116
* <p>Can be overridden by subclasses as necessary.
115117
* @param testClass the test class for which the bootstrap context should be created
116118
* @return a new {@code BootstrapContext}; never {@code null}
117119
*/
118120
protected BootstrapContext createBootstrapContext(Class<?> testClass) {
119-
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate();
120-
return new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
121+
return BootstrapUtils.createBootstrapContext(testClass);
121122
}
122123

123124
/**

spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.core.io.support.SpringFactoriesLoader;
3939
import org.springframework.test.context.BootstrapContext;
4040
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
41+
import org.springframework.test.context.ContextCache;
4142
import org.springframework.test.context.ContextConfiguration;
4243
import org.springframework.test.context.ContextConfigurationAttributes;
4344
import org.springframework.test.context.ContextHierarchy;
@@ -67,6 +68,9 @@
6768
* <li>{@link #processMergedContextConfiguration}
6869
* </ul>
6970
*
71+
* <p>To plug in custom {@link ContextCache} support, override
72+
* {@link #getCacheAwareContextLoaderDelegate()}.
73+
*
7074
* @author Sam Brannen
7175
* @author Juergen Hoeller
7276
* @since 4.1
@@ -96,17 +100,17 @@ public BootstrapContext getBootstrapContext() {
96100

97101
/**
98102
* Build a new {@link DefaultTestContext} using the {@linkplain Class test class}
99-
* and {@link CacheAwareContextLoaderDelegate} in the {@link BootstrapContext}
100-
* associated with this bootstrapper and by delegating to
101-
* {@link #buildMergedContextConfiguration()}.
103+
* in the {@link BootstrapContext} associated with this bootstrapper and
104+
* by delegating to {@link #buildMergedContextConfiguration()} and
105+
* {@link #getCacheAwareContextLoaderDelegate()}.
102106
* <p>Concrete subclasses may choose to override this method to return a
103107
* custom {@link TestContext} implementation.
104108
* @since 4.2
105109
*/
106110
@Override
107111
public TestContext buildTestContext() {
108-
return new DefaultTestContext(bootstrapContext.getTestClass(), buildMergedContextConfiguration(),
109-
bootstrapContext.getCacheAwareContextLoaderDelegate());
112+
return new DefaultTestContext(getBootstrapContext().getTestClass(), buildMergedContextConfiguration(),
113+
getCacheAwareContextLoaderDelegate());
110114
}
111115

112116
/**
@@ -282,7 +286,7 @@ protected List<String> getDefaultTestExecutionListenerClassNames() {
282286
@Override
283287
public final MergedContextConfiguration buildMergedContextConfiguration() {
284288
Class<?> testClass = getBootstrapContext().getTestClass();
285-
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();
289+
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate();
286290

287291
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
288292
ContextHierarchy.class) == null) {
@@ -471,6 +475,20 @@ protected Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
471475
return null;
472476
}
473477

478+
/**
479+
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
480+
* interaction with the {@code ContextCache}.
481+
* <p>The default implementation simply delegates to
482+
* {@code getBootstrapContext().getCacheAwareContextLoaderDelegate()}.
483+
* <p>Concrete subclasses may choose to override this method to return a
484+
* custom {@code CacheAwareContextLoaderDelegate} implementation with
485+
* custom {@link ContextCache} support.
486+
* @return the context loader delegate (never {@code null})
487+
*/
488+
protected CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
489+
return getBootstrapContext().getCacheAwareContextLoaderDelegate();
490+
}
491+
474492
/**
475493
* Determine the default {@link ContextLoader} {@linkplain Class class}
476494
* to use for the supplied test class.

spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java renamed to spring-test/src/main/java/org/springframework/test/context/support/DefaultBootstrapContext.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -14,9 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.test.context;
17+
package org.springframework.test.context.support;
1818

1919
import org.springframework.core.style.ToStringCreator;
20+
import org.springframework.test.context.BootstrapContext;
21+
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
2022
import org.springframework.util.Assert;
2123

2224
/**
@@ -25,13 +27,19 @@
2527
* @author Sam Brannen
2628
* @since 4.1
2729
*/
28-
class DefaultBootstrapContext implements BootstrapContext {
30+
public class DefaultBootstrapContext implements BootstrapContext {
2931

3032
private final Class<?> testClass;
3133
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
3234

3335

34-
DefaultBootstrapContext(Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
36+
/**
37+
* Construct a new {@code DefaultBootstrapContext} from the supplied arguments.
38+
* @param testClass the test class for this bootstrap context; never {@code null}
39+
* @param cacheAwareContextLoaderDelegate the context loader delegate to use for
40+
* transparent interaction with the {@code ContextCache}; never {@code null}
41+
*/
42+
public DefaultBootstrapContext(Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
3543
Assert.notNull(testClass, "Test class must not be null");
3644
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
3745
this.testClass = testClass;

spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java renamed to spring-test/src/main/java/org/springframework/test/context/support/DefaultCacheAwareContextLoaderDelegate.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,37 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.test.context;
17+
package org.springframework.test.context.support;
1818

1919
import org.apache.commons.logging.Log;
2020
import org.apache.commons.logging.LogFactory;
2121

2222
import org.springframework.context.ApplicationContext;
2323
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
24-
import org.springframework.test.context.support.DefaultContextCache;
24+
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
25+
import org.springframework.test.context.ContextCache;
26+
import org.springframework.test.context.ContextLoader;
27+
import org.springframework.test.context.MergedContextConfiguration;
28+
import org.springframework.test.context.SmartContextLoader;
2529
import org.springframework.util.Assert;
2630

2731
/**
2832
* Default implementation of the {@link CacheAwareContextLoaderDelegate} interface.
2933
*
34+
* <p>To use a static {@code DefaultContextCache}, invoke the
35+
* {@link #DefaultCacheAwareContextLoaderDelegate()} constructor; otherwise,
36+
* invoke the {@link #DefaultCacheAwareContextLoaderDelegate(ContextCache)}
37+
* and provide a custom {@link ContextCache} implementation.
38+
*
3039
* @author Sam Brannen
3140
* @since 4.1
3241
*/
33-
class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
42+
public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
3443

3544
private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class);
3645

37-
private static final Log statsLogger = LogFactory.getLog("org.springframework.test.context.cache");
38-
3946
/**
40-
* Default cache of Spring application contexts.
41-
*
42-
* <p>This default cache is static, so that each context can be cached
43-
* and reused for all subsequent tests that declare the same unique
44-
* context configuration within the same JVM process.
47+
* Default static cache of Spring application contexts.
4548
*/
4649
static final ContextCache defaultContextCache = new DefaultContextCache();
4750

@@ -50,28 +53,39 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
5053

5154
/**
5255
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
53-
* a static {@code DefaultContextCache}.
56+
* a static {@link DefaultContextCache}.
57+
* <p>This default cache is static so that each context can be cached
58+
* and reused for all subsequent tests that declare the same unique
59+
* context configuration within the same JVM process.
60+
* @see #DefaultCacheAwareContextLoaderDelegate(ContextCache)
5461
*/
55-
DefaultCacheAwareContextLoaderDelegate() {
62+
public DefaultCacheAwareContextLoaderDelegate() {
5663
this(defaultContextCache);
5764
}
5865

5966
/**
6067
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
61-
* the supplied {@code ContextCache}.
68+
* the supplied {@link ContextCache}.
69+
* @see #DefaultCacheAwareContextLoaderDelegate()
6270
*/
63-
DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
71+
public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
6472
Assert.notNull(contextCache, "ContextCache must not be null");
6573
this.contextCache = contextCache;
6674
}
6775

76+
/**
77+
* Get the {@link ContextCache} used by this context loader delegate.
78+
*/
79+
protected ContextCache getContextCache() {
80+
return this.contextCache;
81+
}
6882

6983
/**
7084
* Load the {@code ApplicationContext} for the supplied merged context configuration.
7185
* <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
7286
* @throws Exception if an error occurs while loading the application context
7387
*/
74-
private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
88+
protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
7589
throws Exception {
7690

7791
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
@@ -118,9 +132,7 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedContextCo
118132
}
119133
}
120134

121-
if (statsLogger.isDebugEnabled()) {
122-
statsLogger.debug("Spring test ApplicationContext cache statistics: " + this.contextCache);
123-
}
135+
this.contextCache.logStatistics();
124136

125137
return context;
126138
}

0 commit comments

Comments
 (0)