Avoid unnecessary synchronization in ContextCache, plus forward-ported polishing

Issue: SPR-12409
This commit is contained in:
Juergen Hoeller 2014-11-06 17:15:30 +01:00
parent 6a96850aa7
commit 74500ec8da
7 changed files with 130 additions and 194 deletions

View File

@ -38,15 +38,13 @@ public interface CacheAwareContextLoaderDelegate {
* Load the {@linkplain ApplicationContext application context} for the supplied * Load the {@linkplain ApplicationContext application context} for the supplied
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader} * {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
* configured in the given {@code MergedContextConfiguration}. * configured in the given {@code MergedContextConfiguration}.
*
* <p>If the context is present in the <em>context cache</em> it will simply * <p>If the context is present in the <em>context cache</em> it will simply
* be returned; otherwise, it will be loaded, stored in the cache, and returned. * be returned; otherwise, it will be loaded, stored in the cache, and returned.
*
* @param mergedContextConfiguration the merged context configuration to use * @param mergedContextConfiguration the merged context configuration to use
* to load the application context; never {@code null} * to load the application context; never {@code null}
* @return the application context * @return the application context
* @throws IllegalStateException if an error occurs while retrieving or * @throws IllegalStateException if an error occurs while retrieving or loading
* loading the application context * the application context
*/ */
ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration); ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration);
@ -55,16 +53,13 @@ public interface CacheAwareContextLoaderDelegate {
* supplied {@link MergedContextConfiguration} from the <em>context cache</em> * supplied {@link MergedContextConfiguration} from the <em>context cache</em>
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is * and {@linkplain ConfigurableApplicationContext#close() close} it if it is
* an instance of {@link ConfigurableApplicationContext}. * an instance of {@link ConfigurableApplicationContext}.
*
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when * <p>The semantics of the supplied {@code HierarchyMode} must be honored when
* removing the context from the cache. See the Javadoc for {@link HierarchyMode} * removing the context from the cache. See the Javadoc for {@link HierarchyMode}
* for details. * for details.
*
* <p>Generally speaking, this method should only be called if the state of * <p>Generally speaking, this method should only be called if the state of
* a singleton bean has been changed (potentially affecting future interaction * a singleton bean has been changed (potentially affecting future interaction
* with the context) or if the context needs to be prematurely removed from * with the context) or if the context needs to be prematurely removed from
* the cache. * the cache.
*
* @param mergedContextConfiguration the merged context configuration for the * @param mergedContextConfiguration the merged context configuration for the
* application context to close; never {@code null} * application context to close; never {@code null}
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context * @param hierarchyMode the hierarchy mode; may be {@code null} if the context

View File

@ -48,13 +48,11 @@ import org.springframework.util.Assert;
*/ */
class ContextCache { class ContextCache {
private final Object monitor = new Object();
/** /**
* Map of context keys to Spring {@code ApplicationContext} instances. * Map of context keys to Spring {@code ApplicationContext} instances.
*/ */
private final Map<MergedContextConfiguration, ApplicationContext> contextMap = new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>( private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
64); new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
/** /**
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em> * Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
@ -62,8 +60,8 @@ class ContextCache {
* need to be recursively removed and closed when removing a context that is a parent * need to be recursively removed and closed when removing a context that is a parent
* of other contexts. * of other contexts.
*/ */
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap = new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>( private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
64); new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
private final AtomicInteger hitCount = new AtomicInteger(); private final AtomicInteger hitCount = new AtomicInteger();
@ -71,67 +69,57 @@ class ContextCache {
/** /**
* Clears all contexts from the cache and clears context hierarchy information as * Clear all contexts from the cache and clears context hierarchy information as well.
* well.
*/ */
void clear() { public void clear() {
synchronized (monitor) { this.contextMap.clear();
this.contextMap.clear(); this.hierarchyMap.clear();
this.hierarchyMap.clear();
}
} }
/** /**
* Clears hit and miss count statistics for the cache (i.e., resets counters to zero). * Clear hit and miss count statistics for the cache (i.e., resets counters to zero).
*/ */
void clearStatistics() { public void clearStatistics() {
this.hitCount.set(0); this.hitCount.set(0);
this.missCount.set(0); this.missCount.set(0);
} }
/** /**
* Return whether there is a cached context for the given key. * Return whether there is a cached context for the given key.
*
* @param key the context key (never {@code null}) * @param key the context key (never {@code null})
*/ */
boolean contains(MergedContextConfiguration key) { public boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
synchronized (monitor) { return this.contextMap.containsKey(key);
return this.contextMap.containsKey(key);
}
} }
/** /**
* Obtain a cached {@code ApplicationContext} for the given key. * Obtain a cached {@code ApplicationContext} for the given key.
* * <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will be * be updated accordingly.
* updated accordingly.
*
* @param key the context key (never {@code null}) * @param key the context key (never {@code null})
* @return the corresponding {@code ApplicationContext} instance, or {@code null} if * @return the corresponding {@code ApplicationContext} instance, or {@code null}
* not found in the cache. * if not found in the cache
* @see #remove * @see #remove
*/ */
ApplicationContext get(MergedContextConfiguration key) { public ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
synchronized (monitor) { ApplicationContext context = this.contextMap.get(key);
ApplicationContext context = this.contextMap.get(key); if (context == null) {
if (context == null) { this.missCount.incrementAndGet();
this.missCount.incrementAndGet();
}
else {
this.hitCount.incrementAndGet();
}
return context;
} }
else {
this.hitCount.incrementAndGet();
}
return context;
} }
/** /**
* Get the overall hit count for this cache. * Get the overall hit count for this cache.
* <p>A <em>hit</em> is an access to the cache, which returned a non-null context for * <p>A <em>hit</em> is an access to the cache, which returned a non-null context
* a queried key. * for a queried key.
*/ */
int getHitCount() { public int getHitCount() {
return this.hitCount.get(); return this.hitCount.get();
} }
@ -140,36 +128,31 @@ class ContextCache {
* <p>A <em>miss</em> is an access to the cache, which returned a {@code null} context * <p>A <em>miss</em> is an access to the cache, which returned a {@code null} context
* for a queried key. * for a queried key.
*/ */
int getMissCount() { public int getMissCount() {
return this.missCount.get(); return this.missCount.get();
} }
/** /**
* Explicitly add an {@code ApplicationContext} instance to the cache under the given * Explicitly add an {@code ApplicationContext} instance to the cache under the given key.
* key.
*
* @param key the context key (never {@code null}) * @param key the context key (never {@code null})
* @param context the {@code ApplicationContext} instance (never {@code null}) * @param context the {@code ApplicationContext} instance (never {@code null})
*/ */
void put(MergedContextConfiguration key, ApplicationContext context) { public void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null"); Assert.notNull(context, "ApplicationContext must not be null");
synchronized (monitor) { this.contextMap.put(key, context);
this.contextMap.put(key, context); MergedContextConfiguration child = key;
MergedContextConfiguration parent = child.getParent();
MergedContextConfiguration child = key; while (parent != null) {
MergedContextConfiguration parent = child.getParent(); Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
while (parent != null) { if (list == null) {
Set<MergedContextConfiguration> list = hierarchyMap.get(parent); list = new HashSet<MergedContextConfiguration>();
if (list == null) { this.hierarchyMap.put(parent, list);
list = new HashSet<MergedContextConfiguration>();
hierarchyMap.put(parent, list);
}
list.add(child);
child = parent;
parent = child.getParent();
} }
list.add(child);
child = parent;
parent = child.getParent();
} }
} }
@ -177,19 +160,16 @@ class ContextCache {
* Remove the context with the given key from the cache and explicitly * Remove the context with the given key from the cache and explicitly
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an * {@linkplain ConfigurableApplicationContext#close() close} it if it is an
* instance of {@link ConfigurableApplicationContext}. * instance of {@link ConfigurableApplicationContext}.
*
* <p>Generally speaking, you would only call this method if you change the * <p>Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with * state of a singleton bean, potentially affecting future interaction with
* the context. * the context.
*
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will * <p>In addition, the semantics of the supplied {@code HierarchyMode} will
* be honored. See the Javadoc for {@link HierarchyMode} for details. * be honored. See the Javadoc for {@link HierarchyMode} for details.
*
* @param key the context key; never {@code null} * @param key the context key; never {@code null}
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context * @param hierarchyMode the hierarchy mode; may be {@code null} if the context
* is not part of a hierarchy * is not part of a hierarchy
*/ */
void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) { public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
// startKey is the level at which to begin clearing the cache, depending // startKey is the level at which to begin clearing the cache, depending
@ -201,24 +181,21 @@ class ContextCache {
} }
} }
synchronized (monitor) { List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
final List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>(); remove(removedContexts, startKey);
remove(removedContexts, startKey); // Remove all remaining references to any removed contexts from the
// hierarchy map.
// Remove all remaining references to any removed contexts from the for (MergedContextConfiguration currentKey : removedContexts) {
// hierarchy map. for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
for (MergedContextConfiguration currentKey : removedContexts) { children.remove(currentKey);
for (Set<MergedContextConfiguration> children : hierarchyMap.values()) {
children.remove(currentKey);
}
} }
}
// Remove empty entries from the hierarchy map. // Remove empty entries from the hierarchy map.
for (MergedContextConfiguration currentKey : hierarchyMap.keySet()) { for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
if (hierarchyMap.get(currentKey).isEmpty()) { if (this.hierarchyMap.get(currentKey).isEmpty()) {
hierarchyMap.remove(currentKey); this.hierarchyMap.remove(currentKey);
}
} }
} }
} }
@ -226,26 +203,23 @@ class ContextCache {
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) { private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null"); Assert.notNull(key, "Key must not be null");
synchronized (monitor) { Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
Set<MergedContextConfiguration> children = hierarchyMap.get(key); if (children != null) {
if (children != null) { for (MergedContextConfiguration child : children) {
for (MergedContextConfiguration child : children) { // Recurse through lower levels
// Recurse through lower levels remove(removedContexts, child);
remove(removedContexts, child);
}
// Remove the set of children for the current context from the
// hierarchy map.
hierarchyMap.remove(key);
} }
// Remove the set of children for the current context from the hierarchy map.
// Physically remove and close leaf nodes first (i.e., on the way back up the this.hierarchyMap.remove(key);
// stack as opposed to prior to the recursive call).
ApplicationContext context = contextMap.remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
removedContexts.add(key);
} }
// Physically remove and close leaf nodes first (i.e., on the way back up the
// stack as opposed to prior to the recursive call).
ApplicationContext context = this.contextMap.remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
removedContexts.add(key);
} }
/** /**
@ -253,34 +227,30 @@ class ContextCache {
* contains more than <tt>Integer.MAX_VALUE</tt> elements, returns * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>. * <tt>Integer.MAX_VALUE</tt>.
*/ */
int size() { public int size() {
synchronized (monitor) { return this.contextMap.size();
return this.contextMap.size();
}
} }
/** /**
* Determine the number of parent contexts currently tracked within the cache. * Determine the number of parent contexts currently tracked within the cache.
*/ */
int getParentContextCount() { public int getParentContextCount() {
synchronized (monitor) { return this.hierarchyMap.size();
return this.hierarchyMap.size();
}
} }
/** /**
* Generates a text string, which contains the {@linkplain #size() size} as well * Generates a text string, which contains the {@linkplain #size() size} as well
* as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss}, and * as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss},
* {@linkplain #getParentContextCount() parent context} counts. * and {@linkplain #getParentContextCount() parent context} counts.
*/ */
@Override @Override
public String toString() { public String toString() {
return new ToStringCreator(this)// return new ToStringCreator(this)
.append("size", size())// .append("size", size())
.append("hitCount", getHitCount())// .append("hitCount", getHitCount())
.append("missCount", getMissCount())// .append("missCount", getMissCount())
.append("parentContextCount", getParentContextCount())// .append("parentContextCount", getParentContextCount())
.toString(); .toString();
} }
} }

View File

@ -48,6 +48,7 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
this.contextCache = contextCache; this.contextCache = contextCache;
} }
/** /**
* Load the {@code ApplicationContext} for the supplied merged context configuration. * Load the {@code ApplicationContext} for the supplied merged context configuration.
* <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs. * <p>Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
@ -55,9 +56,10 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
*/ */
private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration) private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
throws Exception { throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader(); ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " +
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
ApplicationContext applicationContext; ApplicationContext applicationContext;
@ -67,28 +69,26 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
} }
else { else {
String[] locations = mergedContextConfiguration.getLocations(); String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " +
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy."); "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
applicationContext = contextLoader.loadContext(locations); applicationContext = contextLoader.loadContext(locations);
} }
return applicationContext; return applicationContext;
} }
/** @Override
* {@inheritDoc}
*/
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) { public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
synchronized (contextCache) { synchronized (this.contextCache) {
ApplicationContext context = contextCache.get(mergedContextConfiguration); ApplicationContext context = this.contextCache.get(mergedContextConfiguration);
if (context == null) { if (context == null) {
try { try {
context = loadContextInternal(mergedContextConfiguration); context = loadContextInternal(mergedContextConfiguration);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Storing ApplicationContext in cache under key [%s].", logger.debug(String.format("Storing ApplicationContext in cache under key [%s]",
mergedContextConfiguration)); mergedContextConfiguration));
} }
contextCache.put(mergedContextConfiguration, context); this.contextCache.put(mergedContextConfiguration, context);
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException("Failed to load ApplicationContext", ex); throw new IllegalStateException("Failed to load ApplicationContext", ex);
@ -96,25 +96,24 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
} }
else { else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].", logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s]",
mergedContextConfiguration)); mergedContextConfiguration));
} }
} }
if (statsLogger.isDebugEnabled()) { if (statsLogger.isDebugEnabled()) {
statsLogger.debug(String.format("Spring test ApplicationContext cache statistics: %s", contextCache)); statsLogger.debug("Spring test ApplicationContext cache statistics: " + this.contextCache);
} }
return context; return context;
} }
} }
/**
* {@inheritDoc}
*/
@Override @Override
public void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode) { public void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode) {
contextCache.remove(mergedContextConfiguration, hierarchyMode); synchronized (this.contextCache) {
this.contextCache.remove(mergedContextConfiguration, hierarchyMode);
}
} }
} }

View File

@ -67,69 +67,50 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration(); this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration();
} }
/**
* {@inheritDoc}
*/
public ApplicationContext getApplicationContext() { public ApplicationContext getApplicationContext() {
return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration); return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
} }
/**
* {@inheritDoc}
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) { public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
cacheAwareContextLoaderDelegate.closeContext(mergedContextConfiguration, hierarchyMode); this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode);
} }
/**
* {@inheritDoc}
*/
public final Class<?> getTestClass() { public final Class<?> getTestClass() {
return testClass; return this.testClass;
} }
/**
* {@inheritDoc}
*/
public final Object getTestInstance() { public final Object getTestInstance() {
return testInstance; return this.testInstance;
} }
/**
* {@inheritDoc}
*/
public final Method getTestMethod() { public final Method getTestMethod() {
return testMethod; return this.testMethod;
} }
/**
* {@inheritDoc}
*/
public final Throwable getTestException() { public final Throwable getTestException() {
return testException; return this.testException;
} }
/**
* {@inheritDoc}
*/
public void updateState(Object testInstance, Method testMethod, Throwable testException) { public void updateState(Object testInstance, Method testMethod, Throwable testException) {
this.testInstance = testInstance; this.testInstance = testInstance;
this.testMethod = testMethod; this.testMethod = testMethod;
this.testException = testException; this.testException = testException;
} }
/** /**
* Provide a String representation of this test context's state. * Provide a String representation of this test context's state.
*/ */
@Override @Override
public String toString() { public String toString() {
return new ToStringCreator(this)// return new ToStringCreator(this)
.append("testClass", testClass)// .append("testClass", this.testClass)
.append("testInstance", testInstance)// .append("testInstance", this.testInstance)
.append("testMethod", testMethod)// .append("testMethod", this.testMethod)
.append("testException", testException)// .append("testException", this.testException)
.append("mergedContextConfiguration", mergedContextConfiguration)// .append("mergedContextConfiguration", this.mergedContextConfiguration)
.toString(); .toString();
} }
} }

View File

@ -84,9 +84,11 @@ import org.springframework.web.client.support.RestGatewaySupport;
*/ */
public class MockRestServiceServer { public class MockRestServiceServer {
private final List<RequestMatcherClientHttpRequest> expectedRequests = new LinkedList<RequestMatcherClientHttpRequest>(); private final List<RequestMatcherClientHttpRequest> expectedRequests =
new LinkedList<RequestMatcherClientHttpRequest>();
private final List<RequestMatcherClientHttpRequest> actualRequests = new LinkedList<RequestMatcherClientHttpRequest>(); private final List<RequestMatcherClientHttpRequest> actualRequests =
new LinkedList<RequestMatcherClientHttpRequest>();
/** /**
@ -97,10 +99,10 @@ public class MockRestServiceServer {
private MockRestServiceServer() { private MockRestServiceServer() {
} }
/** /**
* Create a {@code MockRestServiceServer} and set up the given * Create a {@code MockRestServiceServer} and set up the given
* {@code RestTemplate} with a mock {@link ClientHttpRequestFactory}. * {@code RestTemplate} with a mock {@link ClientHttpRequestFactory}.
*
* @param restTemplate the RestTemplate to set up for mock testing * @param restTemplate the RestTemplate to set up for mock testing
* @return the created mock server * @return the created mock server
*/ */
@ -115,7 +117,6 @@ public class MockRestServiceServer {
/** /**
* Create a {@code MockRestServiceServer} and set up the given * Create a {@code MockRestServiceServer} and set up the given
* {@code AsyRestTemplate} with a mock {@link AsyncClientHttpRequestFactory}. * {@code AsyRestTemplate} with a mock {@link AsyncClientHttpRequestFactory}.
*
* @param asyncRestTemplate the AsyncRestTemplate to set up for mock testing * @param asyncRestTemplate the AsyncRestTemplate to set up for mock testing
* @return the created mock server * @return the created mock server
*/ */
@ -130,7 +131,6 @@ public class MockRestServiceServer {
/** /**
* Create a {@code MockRestServiceServer} and set up the given * Create a {@code MockRestServiceServer} and set up the given
* {@code RestGatewaySupport} with a mock {@link ClientHttpRequestFactory}. * {@code RestGatewaySupport} with a mock {@link ClientHttpRequestFactory}.
*
* @param restGateway the REST gateway to set up for mock testing * @param restGateway the REST gateway to set up for mock testing
* @return the created mock server * @return the created mock server
*/ */
@ -139,14 +139,12 @@ public class MockRestServiceServer {
return createServer(restGateway.getRestTemplate()); return createServer(restGateway.getRestTemplate());
} }
/** /**
* Set up a new HTTP request expectation. The returned {@link ResponseActions} * Set up a new HTTP request expectation. The returned {@link ResponseActions}
* is used to set up further expectations and to define the response. * is used to set up further expectations and to define the response.
* * <p>This method may be invoked multiple times before starting the test, i.e. before
* <p>This method may be invoked multiple times before starting the test, i.e. * using the {@code RestTemplate}, to set up expectations for multiple requests.
* before using the {@code RestTemplate}, to set up expectations for multiple
* requests.
*
* @param requestMatcher a request expectation, see {@link MockRestRequestMatchers} * @param requestMatcher a request expectation, see {@link MockRestRequestMatchers}
* @return used to set up further expectations or to define a response * @return used to set up further expectations or to define a response
*/ */
@ -160,7 +158,6 @@ public class MockRestServiceServer {
/** /**
* Verify that all expected requests set up via * Verify that all expected requests set up via
* {@link #expect(RequestMatcher)} were indeed performed. * {@link #expect(RequestMatcher)} were indeed performed.
*
* @throws AssertionError when some expectations were not met * @throws AssertionError when some expectations were not met
*/ */
public void verify() { public void verify() {
@ -172,7 +169,6 @@ public class MockRestServiceServer {
private String getVerifyMessage() { private String getVerifyMessage() {
StringBuilder sb = new StringBuilder("Further request(s) expected\n"); StringBuilder sb = new StringBuilder("Further request(s) expected\n");
if (this.actualRequests.size() > 0) { if (this.actualRequests.size() > 0) {
sb.append("The following "); sb.append("The following ");
} }
@ -228,4 +224,4 @@ public class MockRestServiceServer {
} }
} }
} }

View File

@ -24,21 +24,15 @@ package org.springframework.test.context;
*/ */
public abstract class TestContextTestUtils { public abstract class TestContextTestUtils {
private TestContextTestUtils() {
/* no-op */
}
public static TestContext buildTestContext(Class<?> testClass, ContextCache contextCache) { public static TestContext buildTestContext(Class<?> testClass, ContextCache contextCache) {
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate( return buildTestContext(testClass, new DefaultCacheAwareContextLoaderDelegate(contextCache));
contextCache);
return buildTestContext(testClass, null, cacheAwareContextLoaderDelegate);
} }
public static TestContext buildTestContext(Class<?> testClass, String customDefaultContextLoaderClassName, public static TestContext buildTestContext(
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
return new DefaultTestContext(testContextBootstrapper); return new DefaultTestContext(testContextBootstrapper);
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.test.web.client; package org.springframework.test.web.client;
import java.net.URI; import java.net.URI;