Convert ContextCache to interface with default implementation
- ContextCache is now a public interface. - Introduced public DefaultContextCache implementation in the 'support' subpackage. Issue: SPR-12683
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,7 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
/**
|
||||
* A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
|
||||
* #loadContext loading} and {@linkplain #closeContext closing} application
|
||||
* contexts, interacting transparently with a <em>context cache</em> behind
|
||||
* contexts, interacting transparently with a {@link ContextCache} behind
|
||||
* the scenes.
|
||||
*
|
||||
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
|
||||
@@ -38,7 +38,7 @@ public interface CacheAwareContextLoaderDelegate {
|
||||
* Load the {@linkplain ApplicationContext application context} for the supplied
|
||||
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
|
||||
* 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 {@code ContextCache} it will simply
|
||||
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
|
||||
* @param mergedContextConfiguration the merged context configuration to use
|
||||
* to load the application context; never {@code null}
|
||||
@@ -50,7 +50,7 @@ public interface CacheAwareContextLoaderDelegate {
|
||||
|
||||
/**
|
||||
* Remove the {@linkplain ApplicationContext application context} for the
|
||||
* supplied {@link MergedContextConfiguration} from the <em>context cache</em>
|
||||
* supplied {@link MergedContextConfiguration} from the {@code ContextCache}
|
||||
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is
|
||||
* an instance of {@link ConfigurableApplicationContext}.
|
||||
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when
|
||||
|
||||
@@ -16,145 +16,48 @@
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Cache for Spring {@link ApplicationContext ApplicationContexts} in a test
|
||||
* environment.
|
||||
* {@code ContextCache} defines the public API for caching Spring
|
||||
* {@link ApplicationContext ApplicationContexts} within the <em>Spring
|
||||
* TestContext Framework</em>.
|
||||
*
|
||||
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
|
||||
* keyed by {@link MergedContextConfiguration} instances.
|
||||
*
|
||||
* <h3>Rationale</h3>
|
||||
* <p>Caching has significant performance benefits if initializing the context
|
||||
* takes a considerable about of time. Although initializing a Spring context
|
||||
* itself is very quick, some beans in a context, such as a
|
||||
* {@code LocalSessionFactoryBean} for working with Hibernate, may take some
|
||||
* time to initialize. Hence it often makes sense to perform that initialization
|
||||
* only once per test suite.
|
||||
*
|
||||
* <h3>Implementation Details</h3>
|
||||
* <p>{@code ContextCache} maintains a cache of {@code ApplicationContexts}
|
||||
* keyed by {@link MergedContextConfiguration} instances. Behind the scenes,
|
||||
* Spring's {@link ConcurrentReferenceHashMap} is used to store
|
||||
* {@linkplain java.lang.ref.SoftReference soft references} to cached contexts
|
||||
* and {@code MergedContextConfiguration} instances.
|
||||
* <p>Context caching can have significant performance benefits if context
|
||||
* initialization is complex. So, although initializing a Spring context itself
|
||||
* is typically very quick, some beans in a context — for example, an
|
||||
* in-memory database or a {@code LocalSessionFactoryBean} for working with
|
||||
* Hibernate — may take several seconds to initialize. Hence it often
|
||||
* makes sense to perform that initialization only once per test suite.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ConcurrentReferenceHashMap
|
||||
* @since 4.2
|
||||
*/
|
||||
class ContextCache {
|
||||
|
||||
/**
|
||||
* Map of context keys to Spring {@code ApplicationContext} instances.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
|
||||
new ConcurrentReferenceHashMap<MergedContextConfiguration, ApplicationContext>(64);
|
||||
|
||||
/**
|
||||
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
|
||||
* of context hierarchies. This information is used for determining which subtrees
|
||||
* need to be recursively removed and closed when removing a context that is a parent
|
||||
* of other contexts.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
|
||||
new ConcurrentReferenceHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
|
||||
|
||||
private final AtomicInteger hitCount = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger missCount = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* Reset all state maintained by this cache.
|
||||
* @see #clear()
|
||||
* @see #clearStatistics()
|
||||
*/
|
||||
public void reset() {
|
||||
synchronized (contextMap) {
|
||||
clear();
|
||||
clearStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all contexts from the cache and clear context hierarchy information as well.
|
||||
*/
|
||||
public void clear() {
|
||||
synchronized (contextMap) {
|
||||
this.contextMap.clear();
|
||||
this.hierarchyMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
|
||||
*/
|
||||
public void clearStatistics() {
|
||||
synchronized (contextMap) {
|
||||
this.hitCount.set(0);
|
||||
this.missCount.set(0);
|
||||
}
|
||||
}
|
||||
public interface ContextCache {
|
||||
|
||||
/**
|
||||
* Determine whether there is a cached context for the given key.
|
||||
* @param key the context key (never {@code null})
|
||||
* @return {@code true} if the cache contains a context with the given key
|
||||
*/
|
||||
public boolean contains(MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
return this.contextMap.containsKey(key);
|
||||
}
|
||||
boolean contains(MergedContextConfiguration key);
|
||||
|
||||
/**
|
||||
* Obtain a cached {@code ApplicationContext} for the given key.
|
||||
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
|
||||
* be updated accordingly.
|
||||
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
|
||||
* must be updated accordingly.
|
||||
* @param key the context key (never {@code null})
|
||||
* @return the corresponding {@code ApplicationContext} instance, or {@code null}
|
||||
* if not found in the cache
|
||||
* @see #remove
|
||||
*/
|
||||
public ApplicationContext get(MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
ApplicationContext context = this.contextMap.get(key);
|
||||
if (context == null) {
|
||||
this.missCount.incrementAndGet();
|
||||
}
|
||||
else {
|
||||
this.hitCount.incrementAndGet();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the overall hit count for this cache.
|
||||
* <p>A <em>hit</em> is any access to the cache that returns a non-null
|
||||
* context for the queried key.
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return this.hitCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the overall miss count for this cache.
|
||||
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
|
||||
* context for the queried key.
|
||||
*/
|
||||
public int getMissCount() {
|
||||
return this.missCount.get();
|
||||
}
|
||||
ApplicationContext get(MergedContextConfiguration key);
|
||||
|
||||
/**
|
||||
* Explicitly add an {@code ApplicationContext} instance to the cache
|
||||
@@ -162,122 +65,80 @@ class ContextCache {
|
||||
* @param key the context key (never {@code null})
|
||||
* @param context the {@code ApplicationContext} instance (never {@code null})
|
||||
*/
|
||||
public void put(MergedContextConfiguration key, ApplicationContext context) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(context, "ApplicationContext must not be null");
|
||||
|
||||
this.contextMap.put(key, context);
|
||||
MergedContextConfiguration child = key;
|
||||
MergedContextConfiguration parent = child.getParent();
|
||||
while (parent != null) {
|
||||
Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
|
||||
if (list == null) {
|
||||
list = new HashSet<MergedContextConfiguration>();
|
||||
this.hierarchyMap.put(parent, list);
|
||||
}
|
||||
list.add(child);
|
||||
child = parent;
|
||||
parent = child.getParent();
|
||||
}
|
||||
}
|
||||
void put(MergedContextConfiguration key, ApplicationContext context);
|
||||
|
||||
/**
|
||||
* Remove the context with the given key from the cache and explicitly
|
||||
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an
|
||||
* instance of {@link ConfigurableApplicationContext}.
|
||||
* <p>Generally speaking, you would only call this method if you change the
|
||||
* state of a singleton bean, potentially affecting future interaction with
|
||||
* the context.
|
||||
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will
|
||||
* {@linkplain org.springframework.context.ConfigurableApplicationContext#close() close}
|
||||
* it if it is an instance of {@code ConfigurableApplicationContext}.
|
||||
* <p>Generally speaking, this method should be called if the state of
|
||||
* a singleton bean has been modified, potentially affecting future
|
||||
* interaction with the context.
|
||||
* <p>In addition, the semantics of the supplied {@code HierarchyMode} must
|
||||
* be honored. See the Javadoc for {@link HierarchyMode} for details.
|
||||
* @param key the context key; never {@code null}
|
||||
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
|
||||
* is not part of a hierarchy
|
||||
*/
|
||||
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
|
||||
// startKey is the level at which to begin clearing the cache, depending
|
||||
// on the configured hierarchy mode.
|
||||
MergedContextConfiguration startKey = key;
|
||||
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
|
||||
while (startKey.getParent() != null) {
|
||||
startKey = startKey.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
|
||||
remove(removedContexts, startKey);
|
||||
|
||||
// Remove all remaining references to any removed contexts from the
|
||||
// hierarchy map.
|
||||
for (MergedContextConfiguration currentKey : removedContexts) {
|
||||
for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
|
||||
children.remove(currentKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty entries from the hierarchy map.
|
||||
for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
|
||||
if (this.hierarchyMap.get(currentKey).isEmpty()) {
|
||||
this.hierarchyMap.remove(currentKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
|
||||
Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
|
||||
if (children != null) {
|
||||
for (MergedContextConfiguration child : children) {
|
||||
// Recurse through lower levels
|
||||
remove(removedContexts, child);
|
||||
}
|
||||
// Remove the set of children for the current context from the hierarchy map.
|
||||
this.hierarchyMap.remove(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);
|
||||
}
|
||||
void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode);
|
||||
|
||||
/**
|
||||
* Determine the number of contexts currently stored in the cache.
|
||||
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
|
||||
* this method returns {@code Integer.MAX_VALUE}.
|
||||
* this method must return {@code Integer.MAX_VALUE}.
|
||||
*/
|
||||
public int size() {
|
||||
return this.contextMap.size();
|
||||
}
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Determine the number of parent contexts currently tracked within the cache.
|
||||
*/
|
||||
public int getParentContextCount() {
|
||||
return this.hierarchyMap.size();
|
||||
}
|
||||
int getParentContextCount();
|
||||
|
||||
/**
|
||||
* Generate a text string containing the statistics for this cache.
|
||||
* <p>Specifically, the returned string contains the {@linkplain #size},
|
||||
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount() miss count},
|
||||
* and {@linkplain #getParentContextCount() parent context count}.
|
||||
* @return the statistics for this cache
|
||||
* Get the overall hit count for this cache.
|
||||
* <p>A <em>hit</em> is any access to the cache that returns a non-null
|
||||
* context for the queried key.
|
||||
*/
|
||||
int getHitCount();
|
||||
|
||||
/**
|
||||
* Get the overall miss count for this cache.
|
||||
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
|
||||
* context for the queried key.
|
||||
*/
|
||||
int getMissCount();
|
||||
|
||||
/**
|
||||
* Reset all state maintained by this cache including statistics.
|
||||
* @see #clear()
|
||||
* @see #clearStatistics()
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Clear all contexts from the cache, clearing context hierarchy information as well.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
|
||||
*/
|
||||
void clearStatistics();
|
||||
|
||||
/**
|
||||
* Generate a text string containing the implementation type of this
|
||||
* cache and its statistics.
|
||||
* <p>The value returned by this method will be used primarily for
|
||||
* logging purposes.
|
||||
* <p>Specifically, the returned string should contain the name of the
|
||||
* concrete {@code ContextCache} implementation, the {@linkplain #size},
|
||||
* {@linkplain #getParentContextCount() parent context count},
|
||||
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount()
|
||||
* miss count}, and any other information useful in monitoring the
|
||||
* state of this cache.
|
||||
* @return a string representation of this cache, including statistics
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("size", size())
|
||||
.append("hitCount", getHitCount())
|
||||
.append("missCount", getMissCount())
|
||||
.append("parentContextCount", getParentContextCount())
|
||||
.toString();
|
||||
}
|
||||
abstract String toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -21,16 +21,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.context.support.DefaultContextCache;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link CacheAwareContextLoaderDelegate} interface.
|
||||
*
|
||||
* <p>Although {@code DefaultCacheAwareContextLoaderDelegate} was first introduced
|
||||
* in Spring Framework 4.1, the initial implementation of this class was extracted
|
||||
* from the existing code base for {@code CacheAwareContextLoaderDelegate} when
|
||||
* {@code CacheAwareContextLoaderDelegate} was converted into an interface.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
*/
|
||||
@@ -47,21 +43,21 @@ class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderD
|
||||
* and reused for all subsequent tests that declare the same unique
|
||||
* context configuration within the same JVM process.
|
||||
*/
|
||||
static final ContextCache defaultContextCache = new ContextCache();
|
||||
static final ContextCache defaultContextCache = new DefaultContextCache();
|
||||
|
||||
private final ContextCache contextCache;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} that
|
||||
* uses the default, static {@code ContextCache}.
|
||||
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
|
||||
* a static {@code DefaultContextCache}.
|
||||
*/
|
||||
DefaultCacheAwareContextLoaderDelegate() {
|
||||
this(defaultContextCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} with
|
||||
* Construct a new {@code DefaultCacheAwareContextLoaderDelegate} using
|
||||
* the supplied {@code ContextCache}.
|
||||
*/
|
||||
DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
import org.springframework.test.context.ContextCache;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link ContextCache} API.
|
||||
*
|
||||
* <p>Uses Spring's {@link ConcurrentReferenceHashMap} to store
|
||||
* {@linkplain java.lang.ref.SoftReference soft references} to cached
|
||||
* contexts and {@code MergedContextConfiguration} instances.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ConcurrentReferenceHashMap
|
||||
*/
|
||||
public class DefaultContextCache implements ContextCache {
|
||||
|
||||
/**
|
||||
* Map of context keys to Spring {@code ApplicationContext} instances.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
|
||||
new ConcurrentReferenceHashMap<MergedContextConfiguration, ApplicationContext>(64);
|
||||
|
||||
/**
|
||||
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
|
||||
* of context hierarchies. This information is used for determining which subtrees
|
||||
* need to be recursively removed and closed when removing a context that is a parent
|
||||
* of other contexts.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
|
||||
new ConcurrentReferenceHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
|
||||
|
||||
private final AtomicInteger hitCount = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger missCount = new AtomicInteger();
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
return this.contextMap.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ApplicationContext get(MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
ApplicationContext context = this.contextMap.get(key);
|
||||
if (context == null) {
|
||||
this.missCount.incrementAndGet();
|
||||
}
|
||||
else {
|
||||
this.hitCount.incrementAndGet();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void put(MergedContextConfiguration key, ApplicationContext context) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(context, "ApplicationContext must not be null");
|
||||
|
||||
this.contextMap.put(key, context);
|
||||
MergedContextConfiguration child = key;
|
||||
MergedContextConfiguration parent = child.getParent();
|
||||
while (parent != null) {
|
||||
Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
|
||||
if (list == null) {
|
||||
list = new HashSet<MergedContextConfiguration>();
|
||||
this.hierarchyMap.put(parent, list);
|
||||
}
|
||||
list.add(child);
|
||||
child = parent;
|
||||
parent = child.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
|
||||
// startKey is the level at which to begin clearing the cache, depending
|
||||
// on the configured hierarchy mode.
|
||||
MergedContextConfiguration startKey = key;
|
||||
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
|
||||
while (startKey.getParent() != null) {
|
||||
startKey = startKey.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
|
||||
remove(removedContexts, startKey);
|
||||
|
||||
// Remove all remaining references to any removed contexts from the
|
||||
// hierarchy map.
|
||||
for (MergedContextConfiguration currentKey : removedContexts) {
|
||||
for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
|
||||
children.remove(currentKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty entries from the hierarchy map.
|
||||
for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
|
||||
if (this.hierarchyMap.get(currentKey).isEmpty()) {
|
||||
this.hierarchyMap.remove(currentKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
|
||||
Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
|
||||
if (children != null) {
|
||||
for (MergedContextConfiguration child : children) {
|
||||
// Recurse through lower levels
|
||||
remove(removedContexts, child);
|
||||
}
|
||||
// Remove the set of children for the current context from the hierarchy map.
|
||||
this.hierarchyMap.remove(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return this.contextMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getParentContextCount() {
|
||||
return this.hierarchyMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getHitCount() {
|
||||
return this.hitCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getMissCount() {
|
||||
return this.missCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
synchronized (contextMap) {
|
||||
clear();
|
||||
clearStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (contextMap) {
|
||||
this.contextMap.clear();
|
||||
this.hierarchyMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void clearStatistics() {
|
||||
synchronized (contextMap) {
|
||||
this.hitCount.set(0);
|
||||
this.missCount.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("size", size())
|
||||
.append("parentContextCount", getParentContextCount())
|
||||
.append("hitCount", getHitCount())
|
||||
.append("missCount", getMissCount())
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user