1
1
/*
2
- * Copyright 2002-2015 the original author or authors.
2
+ * Copyright 2002-2016 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .test .context .cache ;
18
18
19
19
import java .util .ArrayList ;
20
+ import java .util .Collections ;
20
21
import java .util .HashSet ;
22
+ import java .util .LinkedHashMap ;
21
23
import java .util .List ;
22
24
import java .util .Map ;
23
25
import java .util .Set ;
37
39
/**
38
40
* Default implementation of the {@link ContextCache} API.
39
41
*
40
- * <p>Uses {@link ConcurrentHashMap ConcurrentHashMaps} to cache
41
- * {@link ApplicationContext} and {@link MergedContextConfiguration} instances.
42
+ * <p>Uses a synchronized {@link Map} configured with a maximum size
43
+ * and a <em>least recently used</em> (LRU) eviction policy to cache
44
+ * {@link ApplicationContext} instances.
45
+ *
46
+ * <p>The maximum size may be supplied as a {@linkplain #DefaultContextCache(int)
47
+ * constructor argument} or set via a system property or Spring property named
48
+ * {@code spring.test.context.cache.maxSize}.
42
49
*
43
50
* @author Sam Brannen
44
51
* @author Juergen Hoeller
45
52
* @since 2.5
53
+ * @see ContextCacheUtils#retrieveMaxCacheSize()
46
54
*/
47
55
public class DefaultContextCache implements ContextCache {
48
56
@@ -52,7 +60,7 @@ public class DefaultContextCache implements ContextCache {
52
60
* Map of context keys to Spring {@code ApplicationContext} instances.
53
61
*/
54
62
private final Map <MergedContextConfiguration , ApplicationContext > contextMap =
55
- new ConcurrentHashMap < MergedContextConfiguration , ApplicationContext >( 64 );
63
+ Collections . synchronizedMap ( new LruCache ( 32 , 0.75f ) );
56
64
57
65
/**
58
66
* 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 {
61
69
* of other contexts.
62
70
*/
63
71
private final Map <MergedContextConfiguration , Set <MergedContextConfiguration >> hierarchyMap =
64
- new ConcurrentHashMap <MergedContextConfiguration , Set <MergedContextConfiguration >>(64 );
72
+ new ConcurrentHashMap <MergedContextConfiguration , Set <MergedContextConfiguration >>(32 );
73
+
74
+ private final int maxSize ;
65
75
66
76
private final AtomicInteger hitCount = new AtomicInteger ();
67
77
68
78
private final AtomicInteger missCount = new AtomicInteger ();
69
79
70
80
81
+ /**
82
+ * Create a new {@code DefaultContextCache} using the maximum cache size
83
+ * obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()}.
84
+ * @since 4.3
85
+ * @see #DefaultContextCache(int)
86
+ * @see ContextCacheUtils#retrieveMaxCacheSize()
87
+ */
88
+ public DefaultContextCache () {
89
+ this (ContextCacheUtils .retrieveMaxCacheSize ());
90
+ }
91
+
92
+ /**
93
+ * Create a new {@code DefaultContextCache} using the supplied maximum
94
+ * cache size.
95
+ * @param maxSize the maximum cache size
96
+ * @throws IllegalArgumentException if the supplied {@code maxSize} value
97
+ * is not positive
98
+ * @since 4.3
99
+ * @see #DefaultContextCache()
100
+ */
101
+ public DefaultContextCache (int maxSize ) {
102
+ Assert .isTrue (maxSize > 0 , "maxSize must be positive" );
103
+ this .maxSize = maxSize ;
104
+ }
105
+
106
+
71
107
/**
72
108
* {@inheritDoc}
73
109
*/
@@ -181,6 +217,13 @@ public int size() {
181
217
return this .contextMap .size ();
182
218
}
183
219
220
+ /**
221
+ * Get the maximum size of this cache.
222
+ */
223
+ public int getMaxSize () {
224
+ return this .maxSize ;
225
+ }
226
+
184
227
/**
185
228
* {@inheritDoc}
186
229
*/
@@ -210,7 +253,7 @@ public int getMissCount() {
210
253
*/
211
254
@ Override
212
255
public void reset () {
213
- synchronized (contextMap ) {
256
+ synchronized (this . contextMap ) {
214
257
clear ();
215
258
clearStatistics ();
216
259
}
@@ -221,7 +264,7 @@ public void reset() {
221
264
*/
222
265
@ Override
223
266
public void clear () {
224
- synchronized (contextMap ) {
267
+ synchronized (this . contextMap ) {
225
268
this .contextMap .clear ();
226
269
this .hierarchyMap .clear ();
227
270
}
@@ -232,7 +275,7 @@ public void clear() {
232
275
*/
233
276
@ Override
234
277
public void clearStatistics () {
235
- synchronized (contextMap ) {
278
+ synchronized (this . contextMap ) {
236
279
this .hitCount .set (0 );
237
280
this .missCount .set (0 );
238
281
}
@@ -259,10 +302,46 @@ public void logStatistics() {
259
302
public String toString () {
260
303
return new ToStringCreator (this )
261
304
.append ("size" , size ())
305
+ .append ("maxSize" , getMaxSize ())
262
306
.append ("parentContextCount" , getParentContextCount ())
263
307
.append ("hitCount" , getHitCount ())
264
308
.append ("missCount" , getMissCount ())
265
309
.toString ();
266
310
}
267
311
312
+
313
+ /**
314
+ * Simple cache implementation based on {@link LinkedHashMap} with a maximum
315
+ * size and a <em>least recently used</em> (LRU) eviction policy that
316
+ * properly closes application contexts.
317
+ *
318
+ * @author Sam Brannen
319
+ * @since 4.3
320
+ */
321
+ @ SuppressWarnings ("serial" )
322
+ private class LruCache extends LinkedHashMap <MergedContextConfiguration , ApplicationContext > {
323
+
324
+ /**
325
+ * Create a new {@code LruCache} with the supplied initial capacity and
326
+ * load factor.
327
+ * @param initialCapacity the initial capacity
328
+ * @param loadFactor the load factor
329
+ */
330
+ LruCache (int initialCapacity , float loadFactor ) {
331
+ super (initialCapacity , loadFactor , true );
332
+ }
333
+
334
+ @ Override
335
+ protected boolean removeEldestEntry (Map .Entry <MergedContextConfiguration , ApplicationContext > eldest ) {
336
+ if (this .size () > DefaultContextCache .this .getMaxSize ()) {
337
+ // Do NOT delete "DefaultContextCache.this."; otherwise, we accidentally
338
+ // invoke java.util.Map.remove(Object, Object).
339
+ DefaultContextCache .this .remove (eldest .getKey (), HierarchyMode .CURRENT_LEVEL );
340
+ }
341
+
342
+ // Return false since we invoke a custom eviction algorithm.
343
+ return false ;
344
+ }
345
+ }
346
+
268
347
}
0 commit comments