diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
index f0b25d6d3b..f5c3499428 100644
--- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java
+++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
@@ -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 {
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
index 895109fa59..e79163336e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java
@@ -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.
*
*
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.
*
*
Rationale
* 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.
+ *
May alternatively be configured via
+ * {@link org.springframework.core.SpringProperties SpringProperties}.
+ *
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.
- *
The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
- * must be updated accordingly.
+ *
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}.
- *
Generally speaking, this method should be called if the state of
- * a singleton bean has been modified, potentially affecting future
- * interaction with the context.
+ *
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.
*
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}
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
new file mode 100644
index 0000000000..c4df35ce1b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java
@@ -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}.
+ *
Uses {@link SpringProperties} to retrieve a system property or Spring
+ * property named {@code spring.test.context.cache.maxSize}.
+ *
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;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
index 0967813594..e6ae3897be 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java
@@ -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.
*
- *
Uses {@link ConcurrentHashMap ConcurrentHashMaps} to cache
- * {@link ApplicationContext} and {@link MergedContextConfiguration} instances.
+ *
Uses a synchronized {@link Map} configured with a maximum size
+ * and a least recently used (LRU) eviction policy to cache
+ * {@link ApplicationContext} instances.
+ *
+ *
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 contextMap =
- new ConcurrentHashMap(64);
+ Collections.synchronizedMap(new LruCache(32, 0.75f));
/**
* Map of parent keys to sets of children keys, representing a top-down tree
@@ -61,13 +69,41 @@ public class DefaultContextCache implements ContextCache {
* of other contexts.
*/
private final Map> hierarchyMap =
- new ConcurrentHashMap>(64);
+ new ConcurrentHashMap>(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 least recently used (LRU) eviction policy that
+ * properly closes application contexts.
+ *
+ * @author Sam Brannen
+ * @since 4.3
+ */
+ @SuppressWarnings("serial")
+ private class LruCache extends LinkedHashMap {
+
+ /**
+ * 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 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;
+ }
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
index 5d518d3c8f..4624fcb74d 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java
@@ -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 {
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
new file mode 100644
index 0000000000..ac6cc72332
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java
@@ -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());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
new file mode 100644
index 0000000000..4ae97b614c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java
@@ -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 contextMap =
+ (Map) ReflectionTestUtils.getField(cache, "contextMap");
+
+ // @formatter:off
+ List 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 {}
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
index 44962e7660..bde994dc0f 100644
--- a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java
@@ -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)
diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc
index 15643ccb26..03fa76f07d 100644
--- a/src/asciidoc/whats-new.adoc
+++ b/src/asciidoc/whats-new.adoc
@@ -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