Avoid unnecessary synchronization in ContextCache, plus forward-ported polishing
Issue: SPR-12409
This commit is contained in:
parent
6a96850aa7
commit
74500ec8da
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue