Limit size of context cache in the TestContext framework
Prior to this commit, the size of the ApplicationContext cache in the Spring TestContext Framework could grow without bound, leading to issues with memory and performance in large test suites. This commit addresses this issue by introducing support for setting the maximum cache size via a JVM system property or Spring property called "spring.test.context.cache.maxSize". If no such property is set, a default value of 32 will be used. Furthermore, the DefaultContextCache has been refactored to use a synchronized LRU cache internally instead of a ConcurrentHashMap. The LRU cache is a simple bounded cache with a "least recently used" (LRU) eviction policy. Issue: SPR-8055
This commit is contained in:
parent
26378cd604
commit
e18d5b591a
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -42,6 +42,7 @@ import org.apache.commons.logging.LogFactory;
|
|||
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
|
||||
* @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
|
||||
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
|
||||
* @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
|
||||
*/
|
||||
public abstract class SpringProperties {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -26,7 +26,9 @@ import org.springframework.test.context.MergedContextConfiguration;
|
|||
* TestContext Framework</em>.
|
||||
*
|
||||
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
|
||||
* keyed by {@link MergedContextConfiguration} instances.
|
||||
* keyed by {@link MergedContextConfiguration} instances, potentially
|
||||
* configured with a {@linkplain ContextCacheUtils#retrieveMaxCacheSize
|
||||
* maximum size} and a custom eviction policy.
|
||||
*
|
||||
* <h3>Rationale</h3>
|
||||
* <p>Context caching can have significant performance benefits if context
|
||||
|
|
@ -40,6 +42,7 @@ import org.springframework.test.context.MergedContextConfiguration;
|
|||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.2
|
||||
* @see ContextCacheUtils#retrieveMaxCacheSize()
|
||||
*/
|
||||
public interface ContextCache {
|
||||
|
||||
|
|
@ -49,6 +52,24 @@ public interface ContextCache {
|
|||
*/
|
||||
public static final String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache";
|
||||
|
||||
/**
|
||||
* The default maximum size of the context cache: {@value #DEFAULT_MAX_CONTEXT_CACHE_SIZE}.
|
||||
* @see #MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
|
||||
*/
|
||||
public static final int DEFAULT_MAX_CONTEXT_CACHE_SIZE = 32;
|
||||
|
||||
/**
|
||||
* System property used to configure the maximum size of the {@link ContextCache}
|
||||
* as a positive integer.
|
||||
* <p>May alternatively be configured via
|
||||
* {@link org.springframework.core.SpringProperties SpringProperties}.
|
||||
* <p>Note that implementations of {@code ContextCache} are not required
|
||||
* to support a maximum cache size. Consult the documentation of the
|
||||
* corresponding implementation for details.
|
||||
* @see #DEFAULT_MAX_CONTEXT_CACHE_SIZE
|
||||
*/
|
||||
public static final String MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME = "spring.test.context.cache.maxSize";
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether there is a cached context for the given key.
|
||||
|
|
@ -59,8 +80,8 @@ public interface ContextCache {
|
|||
|
||||
/**
|
||||
* Obtain a cached {@code ApplicationContext} for the given key.
|
||||
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
|
||||
* must be updated accordingly.
|
||||
* <p>The {@linkplain #getHitCount() hit} and {@linkplain #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
|
||||
|
|
@ -70,7 +91,7 @@ public interface ContextCache {
|
|||
|
||||
/**
|
||||
* Explicitly add an {@code ApplicationContext} instance to the cache
|
||||
* under the given key.
|
||||
* under the given key, potentially honoring a custom eviction policy.
|
||||
* @param key the context key (never {@code null})
|
||||
* @param context the {@code ApplicationContext} instance (never {@code null})
|
||||
*/
|
||||
|
|
@ -80,9 +101,10 @@ public interface ContextCache {
|
|||
* Remove the context with the given key from the cache and explicitly
|
||||
* {@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>Generally speaking, this method should be called to properly evict
|
||||
* a context from the cache (e.g., due to a custom eviction policy) or 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}
|
||||
|
|
|
|||
59
spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
vendored
Normal file
59
spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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.cache;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Collection of utilities for working with {@link ContextCache ContextCaches}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.3
|
||||
*/
|
||||
public abstract class ContextCacheUtils {
|
||||
|
||||
private ContextCacheUtils() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the maximum size of the {@link ContextCache}.
|
||||
* <p>Uses {@link SpringProperties} to retrieve a system property or Spring
|
||||
* property named {@code spring.test.context.cache.maxSize}.
|
||||
* <p>Falls back to the value of the {@link ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE}
|
||||
* if no such property has been set or if the property is not an integer.
|
||||
* @return the maximum size of the context cache
|
||||
* @see ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
|
||||
*/
|
||||
public static int retrieveMaxCacheSize() {
|
||||
try {
|
||||
String maxSize = SpringProperties.getProperty(ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
|
||||
if (StringUtils.hasText(maxSize)) {
|
||||
return Integer.parseInt(maxSize.trim());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -17,7 +17,9 @@
|
|||
package org.springframework.test.context.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -37,12 +39,18 @@ import org.springframework.util.Assert;
|
|||
/**
|
||||
* Default implementation of the {@link ContextCache} API.
|
||||
*
|
||||
* <p>Uses {@link ConcurrentHashMap ConcurrentHashMaps} to cache
|
||||
* {@link ApplicationContext} and {@link MergedContextConfiguration} instances.
|
||||
* <p>Uses a synchronized {@link Map} configured with a maximum size
|
||||
* and a <em>least recently used</em> (LRU) eviction policy to cache
|
||||
* {@link ApplicationContext} instances.
|
||||
*
|
||||
* <p>The maximum size may be supplied as a {@linkplain #DefaultContextCache(int)
|
||||
* constructor argument} or set via a system property or Spring property named
|
||||
* {@code spring.test.context.cache.maxSize}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ContextCacheUtils#retrieveMaxCacheSize()
|
||||
*/
|
||||
public class DefaultContextCache implements ContextCache {
|
||||
|
||||
|
|
@ -52,7 +60,7 @@ public class DefaultContextCache implements ContextCache {
|
|||
* Map of context keys to Spring {@code ApplicationContext} instances.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
|
||||
new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
|
||||
Collections.synchronizedMap(new LruCache(32, 0.75f));
|
||||
|
||||
/**
|
||||
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
|
||||
|
|
@ -61,13 +69,41 @@ public class DefaultContextCache implements ContextCache {
|
|||
* of other contexts.
|
||||
*/
|
||||
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
|
||||
new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
|
||||
new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(32);
|
||||
|
||||
private final int maxSize;
|
||||
|
||||
private final AtomicInteger hitCount = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger missCount = new AtomicInteger();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code DefaultContextCache} using the maximum cache size
|
||||
* obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()}.
|
||||
* @since 4.3
|
||||
* @see #DefaultContextCache(int)
|
||||
* @see ContextCacheUtils#retrieveMaxCacheSize()
|
||||
*/
|
||||
public DefaultContextCache() {
|
||||
this(ContextCacheUtils.retrieveMaxCacheSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code DefaultContextCache} using the supplied maximum
|
||||
* cache size.
|
||||
* @param maxSize the maximum cache size
|
||||
* @throws IllegalArgumentException if the supplied {@code maxSize} value
|
||||
* is not positive
|
||||
* @since 4.3
|
||||
* @see #DefaultContextCache()
|
||||
*/
|
||||
public DefaultContextCache(int maxSize) {
|
||||
Assert.isTrue(maxSize > 0, "maxSize must be positive");
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
@ -181,6 +217,13 @@ public class DefaultContextCache implements ContextCache {
|
|||
return this.contextMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum size of this cache.
|
||||
*/
|
||||
public int getMaxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
@ -210,7 +253,7 @@ public class DefaultContextCache implements ContextCache {
|
|||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
synchronized (contextMap) {
|
||||
synchronized (this.contextMap) {
|
||||
clear();
|
||||
clearStatistics();
|
||||
}
|
||||
|
|
@ -221,7 +264,7 @@ public class DefaultContextCache implements ContextCache {
|
|||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (contextMap) {
|
||||
synchronized (this.contextMap) {
|
||||
this.contextMap.clear();
|
||||
this.hierarchyMap.clear();
|
||||
}
|
||||
|
|
@ -232,7 +275,7 @@ public class DefaultContextCache implements ContextCache {
|
|||
*/
|
||||
@Override
|
||||
public void clearStatistics() {
|
||||
synchronized (contextMap) {
|
||||
synchronized (this.contextMap) {
|
||||
this.hitCount.set(0);
|
||||
this.missCount.set(0);
|
||||
}
|
||||
|
|
@ -259,10 +302,46 @@ public class DefaultContextCache implements ContextCache {
|
|||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("size", size())
|
||||
.append("maxSize", getMaxSize())
|
||||
.append("parentContextCount", getParentContextCount())
|
||||
.append("hitCount", getHitCount())
|
||||
.append("missCount", getMissCount())
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple cache implementation based on {@link LinkedHashMap} with a maximum
|
||||
* size and a <em>least recently used</em> (LRU) eviction policy that
|
||||
* properly closes application contexts.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private class LruCache extends LinkedHashMap<MergedContextConfiguration, ApplicationContext> {
|
||||
|
||||
/**
|
||||
* Create a new {@code LruCache} with the supplied initial capacity and
|
||||
* load factor.
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
*/
|
||||
LruCache(int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<MergedContextConfiguration, ApplicationContext> eldest) {
|
||||
if (this.size() > DefaultContextCache.this.getMaxSize()) {
|
||||
// Do NOT delete "DefaultContextCache.this."; otherwise, we accidentally
|
||||
// invoke java.util.Map.remove(Object, Object).
|
||||
DefaultContextCache.this.remove(eldest.getKey(), HierarchyMode.CURRENT_LEVEL);
|
||||
}
|
||||
|
||||
// Return false since we invoke a custom eviction algorithm.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -42,6 +42,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
|
|||
* @author Sam Brannen
|
||||
* @author Michail Nikolaev
|
||||
* @since 3.1
|
||||
* @see LruContextCacheTests
|
||||
* @see SpringRunnerContextCacheTests
|
||||
*/
|
||||
public class ContextCacheTests {
|
||||
|
|
|
|||
89
spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
vendored
Normal file
89
spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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.cache;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.context.cache.ContextCacheUtils.*;
|
||||
import static org.springframework.test.context.cache.ContextCache.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ContextCacheUtils}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.3
|
||||
*/
|
||||
public class ContextCacheUtilsTests {
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void clearProperties() {
|
||||
System.clearProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME);
|
||||
SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromDefault() {
|
||||
assertDefaultValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromBogusSystemProperty() {
|
||||
System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
|
||||
assertDefaultValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromBogusSpringProperty() {
|
||||
SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus");
|
||||
assertDefaultValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromDecimalSpringProperty() {
|
||||
SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "3.14");
|
||||
assertDefaultValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromSystemProperty() {
|
||||
System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42");
|
||||
assertEquals(42, retrieveMaxCacheSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromSystemPropertyContainingWhitespace() {
|
||||
System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42\t");
|
||||
assertEquals(42, retrieveMaxCacheSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveMaxCacheSizeFromSpringProperty() {
|
||||
SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "99");
|
||||
assertEquals(99, retrieveMaxCacheSize());
|
||||
}
|
||||
|
||||
private static void assertDefaultValue() {
|
||||
assertEquals(DEFAULT_MAX_CONTEXT_CACHE_SIZE, retrieveMaxCacheSize());
|
||||
}
|
||||
|
||||
}
|
||||
177
spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
vendored
Normal file
177
spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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.cache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for the LRU eviction policy in {@link DefaultContextCache}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.3
|
||||
* @see ContextCacheTests
|
||||
*/
|
||||
public class LruContextCacheTests {
|
||||
|
||||
private static final MergedContextConfiguration abcConfig = config(Abc.class);
|
||||
private static final MergedContextConfiguration fooConfig = config(Foo.class);
|
||||
private static final MergedContextConfiguration barConfig = config(Bar.class);
|
||||
private static final MergedContextConfiguration bazConfig = config(Baz.class);
|
||||
|
||||
|
||||
private final ConfigurableApplicationContext abcContext = mock(ConfigurableApplicationContext.class);
|
||||
private final ConfigurableApplicationContext fooContext = mock(ConfigurableApplicationContext.class);
|
||||
private final ConfigurableApplicationContext barContext = mock(ConfigurableApplicationContext.class);
|
||||
private final ConfigurableApplicationContext bazContext = mock(ConfigurableApplicationContext.class);
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void maxCacheSizeNegativeOne() {
|
||||
new DefaultContextCache(-1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void maxCacheSizeZero() {
|
||||
new DefaultContextCache(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxCacheSizeOne() {
|
||||
DefaultContextCache cache = new DefaultContextCache(1);
|
||||
assertEquals(0, cache.size());
|
||||
assertEquals(1, cache.getMaxSize());
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
assertCacheContents(cache, "Foo");
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
assertCacheContents(cache, "Foo");
|
||||
|
||||
cache.put(barConfig, barContext);
|
||||
assertCacheContents(cache, "Bar");
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
assertCacheContents(cache, "Foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxCacheSizeThree() {
|
||||
DefaultContextCache cache = new DefaultContextCache(3);
|
||||
assertEquals(0, cache.size());
|
||||
assertEquals(3, cache.getMaxSize());
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
assertCacheContents(cache, "Foo");
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
assertCacheContents(cache, "Foo");
|
||||
|
||||
cache.put(barConfig, barContext);
|
||||
assertCacheContents(cache, "Foo", "Bar");
|
||||
|
||||
cache.put(bazConfig, bazContext);
|
||||
assertCacheContents(cache, "Foo", "Bar", "Baz");
|
||||
|
||||
cache.put(abcConfig, abcContext);
|
||||
assertCacheContents(cache, "Bar", "Baz", "Abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureLruOrderingIsUpdated() {
|
||||
DefaultContextCache cache = new DefaultContextCache(3);
|
||||
|
||||
// Note: when a new entry is added it is considered the MRU entry and inserted at the tail.
|
||||
cache.put(fooConfig, fooContext);
|
||||
cache.put(barConfig, barContext);
|
||||
cache.put(bazConfig, bazContext);
|
||||
assertCacheContents(cache, "Foo", "Bar", "Baz");
|
||||
|
||||
// Note: the MRU entry is moved to the tail when accessed.
|
||||
cache.get(fooConfig);
|
||||
assertCacheContents(cache, "Bar", "Baz", "Foo");
|
||||
|
||||
cache.get(barConfig);
|
||||
assertCacheContents(cache, "Baz", "Foo", "Bar");
|
||||
|
||||
cache.get(bazConfig);
|
||||
assertCacheContents(cache, "Foo", "Bar", "Baz");
|
||||
|
||||
cache.get(barConfig);
|
||||
assertCacheContents(cache, "Foo", "Baz", "Bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureEvictedContextsAreClosed() {
|
||||
DefaultContextCache cache = new DefaultContextCache(2);
|
||||
|
||||
cache.put(fooConfig, fooContext);
|
||||
cache.put(barConfig, barContext);
|
||||
assertCacheContents(cache, "Foo", "Bar");
|
||||
|
||||
cache.put(bazConfig, bazContext);
|
||||
assertCacheContents(cache, "Bar", "Baz");
|
||||
verify(fooContext, times(1)).close();
|
||||
|
||||
cache.put(abcConfig, abcContext);
|
||||
assertCacheContents(cache, "Baz", "Abc");
|
||||
verify(barContext, times(1)).close();
|
||||
|
||||
verify(abcContext, never()).close();
|
||||
verify(bazContext, never()).close();
|
||||
}
|
||||
|
||||
|
||||
private static MergedContextConfiguration config(Class<?> clazz) {
|
||||
return new MergedContextConfiguration(null, null, new Class<?>[] { clazz }, null, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void assertCacheContents(DefaultContextCache cache, String... expectedNames) {
|
||||
|
||||
Map<MergedContextConfiguration, ApplicationContext> contextMap =
|
||||
(Map<MergedContextConfiguration, ApplicationContext>) ReflectionTestUtils.getField(cache, "contextMap");
|
||||
|
||||
// @formatter:off
|
||||
List<String> actualNames = contextMap.keySet().stream()
|
||||
.map(cfg -> cfg.getClasses()[0])
|
||||
.map(Class::getSimpleName)
|
||||
.collect(toList());
|
||||
// @formatter:on
|
||||
|
||||
assertEquals(asList(expectedNames), actualNames);
|
||||
}
|
||||
|
||||
|
||||
private static class Abc {}
|
||||
private static class Foo {}
|
||||
private static class Bar {}
|
||||
private static class Baz {}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -45,6 +45,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*;
|
|||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ContextCacheTests
|
||||
* @see LruContextCacheTests
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
|
|
|
|||
|
|
@ -696,6 +696,10 @@ Spring 4.3 also improves the caching abstraction as follows:
|
|||
XML files, Groovy scripts, or `@Configuration` classes are detected.
|
||||
* `@Transactional` test methods are no longer required to be `public` (in TestNG and JUnit 5).
|
||||
* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public`.
|
||||
* The `ApplicationContext` cache in the _Spring TestContext Framework_ is now bounded with a
|
||||
default maximum size of 32 and a _least recently used_ eviction policy. The maximum size
|
||||
can be configured by setting a JVM system property or Spring property called
|
||||
`spring.test.context.cache.maxSize`.
|
||||
* New `ContextCustomizer` API for customizing a test `ApplicationContext` _after_ bean
|
||||
definitions have been loaded into the context but _before_ the context has been refreshed.
|
||||
Customizers can be registered globally by third parties, foregoing the need to implement a
|
||||
|
|
|
|||
Loading…
Reference in New Issue