Skip to content

Commit e6c24f7

Browse files
committed
Convert ContextCache to interface with default implementation
- ContextCache is now a public interface. - Introduced public DefaultContextCache implementation in the 'support' subpackage. Issue: SPR-12683
1 parent c9d597f commit e6c24f7

File tree

5 files changed

+333
-224
lines changed

5 files changed

+333
-224
lines changed

spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
2323
/**
2424
* A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
2525
* #loadContext loading} and {@linkplain #closeContext closing} application
26-
* contexts, interacting transparently with a <em>context cache</em> behind
26+
* contexts, interacting transparently with a {@link ContextCache} behind
2727
* the scenes.
2828
*
2929
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not extend the
@@ -38,7 +38,7 @@ public interface CacheAwareContextLoaderDelegate {
3838
* Load the {@linkplain ApplicationContext application context} for the supplied
3939
* {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
4040
* configured in the given {@code MergedContextConfiguration}.
41-
* <p>If the context is present in the <em>context cache</em> it will simply
41+
* <p>If the context is present in the {@code ContextCache} it will simply
4242
* be returned; otherwise, it will be loaded, stored in the cache, and returned.
4343
* @param mergedContextConfiguration the merged context configuration to use
4444
* to load the application context; never {@code null}
@@ -50,7 +50,7 @@ public interface CacheAwareContextLoaderDelegate {
5050

5151
/**
5252
* Remove the {@linkplain ApplicationContext application context} for the
53-
* supplied {@link MergedContextConfiguration} from the <em>context cache</em>
53+
* supplied {@link MergedContextConfiguration} from the {@code ContextCache}
5454
* and {@linkplain ConfigurableApplicationContext#close() close} it if it is
5555
* an instance of {@link ConfigurableApplicationContext}.
5656
* <p>The semantics of the supplied {@code HierarchyMode} must be honored when

spring-test/src/main/java/org/springframework/test/context/ContextCache.java

Lines changed: 70 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -16,268 +16,129 @@
1616

1717
package org.springframework.test.context;
1818

19-
import java.util.ArrayList;
20-
import java.util.HashSet;
21-
import java.util.List;
22-
import java.util.Map;
23-
import java.util.Set;
24-
import java.util.concurrent.atomic.AtomicInteger;
25-
2619
import org.springframework.context.ApplicationContext;
27-
import org.springframework.context.ConfigurableApplicationContext;
28-
import org.springframework.core.style.ToStringCreator;
2920
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
30-
import org.springframework.util.Assert;
31-
import org.springframework.util.ConcurrentReferenceHashMap;
3221

3322
/**
34-
* Cache for Spring {@link ApplicationContext ApplicationContexts} in a test
35-
* environment.
23+
* {@code ContextCache} defines the public API for caching Spring
24+
* {@link ApplicationContext ApplicationContexts} within the <em>Spring
25+
* TestContext Framework</em>.
3626
*
37-
* <h3>Rationale</h3>
38-
* <p>Caching has significant performance benefits if initializing the context
39-
* takes a considerable about of time. Although initializing a Spring context
40-
* itself is very quick, some beans in a context, such as a
41-
* {@code LocalSessionFactoryBean} for working with Hibernate, may take some
42-
* time to initialize. Hence it often makes sense to perform that initialization
43-
* only once per test suite.
27+
* <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts}
28+
* keyed by {@link MergedContextConfiguration} instances.
4429
*
45-
* <h3>Implementation Details</h3>
46-
* <p>{@code ContextCache} maintains a cache of {@code ApplicationContexts}
47-
* keyed by {@link MergedContextConfiguration} instances. Behind the scenes,
48-
* Spring's {@link ConcurrentReferenceHashMap} is used to store
49-
* {@linkplain java.lang.ref.SoftReference soft references} to cached contexts
50-
* and {@code MergedContextConfiguration} instances.
30+
* <h3>Rationale</h3>
31+
* <p>Context caching can have significant performance benefits if context
32+
* initialization is complex. So, although initializing a Spring context itself
33+
* is typically very quick, some beans in a context &mdash; for example, an
34+
* in-memory database or a {@code LocalSessionFactoryBean} for working with
35+
* Hibernate &mdash; may take several seconds to initialize. Hence it often
36+
* makes sense to perform that initialization only once per test suite.
5137
*
5238
* @author Sam Brannen
5339
* @author Juergen Hoeller
54-
* @since 2.5
55-
* @see ConcurrentReferenceHashMap
40+
* @since 4.2
5641
*/
57-
class ContextCache {
58-
59-
/**
60-
* Map of context keys to Spring {@code ApplicationContext} instances.
61-
*/
62-
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
63-
new ConcurrentReferenceHashMap<MergedContextConfiguration, ApplicationContext>(64);
64-
65-
/**
66-
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
67-
* of context hierarchies. This information is used for determining which subtrees
68-
* need to be recursively removed and closed when removing a context that is a parent
69-
* of other contexts.
70-
*/
71-
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
72-
new ConcurrentReferenceHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
73-
74-
private final AtomicInteger hitCount = new AtomicInteger();
75-
76-
private final AtomicInteger missCount = new AtomicInteger();
77-
78-
/**
79-
* Reset all state maintained by this cache.
80-
* @see #clear()
81-
* @see #clearStatistics()
82-
*/
83-
public void reset() {
84-
synchronized (contextMap) {
85-
clear();
86-
clearStatistics();
87-
}
88-
}
89-
90-
/**
91-
* Clear all contexts from the cache and clear context hierarchy information as well.
92-
*/
93-
public void clear() {
94-
synchronized (contextMap) {
95-
this.contextMap.clear();
96-
this.hierarchyMap.clear();
97-
}
98-
}
99-
100-
/**
101-
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
102-
*/
103-
public void clearStatistics() {
104-
synchronized (contextMap) {
105-
this.hitCount.set(0);
106-
this.missCount.set(0);
107-
}
108-
}
42+
public interface ContextCache {
10943

11044
/**
11145
* Determine whether there is a cached context for the given key.
11246
* @param key the context key (never {@code null})
11347
* @return {@code true} if the cache contains a context with the given key
11448
*/
115-
public boolean contains(MergedContextConfiguration key) {
116-
Assert.notNull(key, "Key must not be null");
117-
return this.contextMap.containsKey(key);
118-
}
49+
boolean contains(MergedContextConfiguration key);
11950

12051
/**
12152
* Obtain a cached {@code ApplicationContext} for the given key.
122-
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
123-
* be updated accordingly.
53+
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts
54+
* must be updated accordingly.
12455
* @param key the context key (never {@code null})
12556
* @return the corresponding {@code ApplicationContext} instance, or {@code null}
12657
* if not found in the cache
12758
* @see #remove
12859
*/
129-
public ApplicationContext get(MergedContextConfiguration key) {
130-
Assert.notNull(key, "Key must not be null");
131-
ApplicationContext context = this.contextMap.get(key);
132-
if (context == null) {
133-
this.missCount.incrementAndGet();
134-
}
135-
else {
136-
this.hitCount.incrementAndGet();
137-
}
138-
return context;
139-
}
140-
141-
/**
142-
* Get the overall hit count for this cache.
143-
* <p>A <em>hit</em> is any access to the cache that returns a non-null
144-
* context for the queried key.
145-
*/
146-
public int getHitCount() {
147-
return this.hitCount.get();
148-
}
149-
150-
/**
151-
* Get the overall miss count for this cache.
152-
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
153-
* context for the queried key.
154-
*/
155-
public int getMissCount() {
156-
return this.missCount.get();
157-
}
60+
ApplicationContext get(MergedContextConfiguration key);
15861

15962
/**
16063
* Explicitly add an {@code ApplicationContext} instance to the cache
16164
* under the given key.
16265
* @param key the context key (never {@code null})
16366
* @param context the {@code ApplicationContext} instance (never {@code null})
16467
*/
165-
public void put(MergedContextConfiguration key, ApplicationContext context) {
166-
Assert.notNull(key, "Key must not be null");
167-
Assert.notNull(context, "ApplicationContext must not be null");
168-
169-
this.contextMap.put(key, context);
170-
MergedContextConfiguration child = key;
171-
MergedContextConfiguration parent = child.getParent();
172-
while (parent != null) {
173-
Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
174-
if (list == null) {
175-
list = new HashSet<MergedContextConfiguration>();
176-
this.hierarchyMap.put(parent, list);
177-
}
178-
list.add(child);
179-
child = parent;
180-
parent = child.getParent();
181-
}
182-
}
68+
void put(MergedContextConfiguration key, ApplicationContext context);
18369

18470
/**
18571
* Remove the context with the given key from the cache and explicitly
186-
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an
187-
* instance of {@link ConfigurableApplicationContext}.
188-
* <p>Generally speaking, you would only call this method if you change the
189-
* state of a singleton bean, potentially affecting future interaction with
190-
* the context.
191-
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will
72+
* {@linkplain org.springframework.context.ConfigurableApplicationContext#close() close}
73+
* it if it is an instance of {@code ConfigurableApplicationContext}.
74+
* <p>Generally speaking, this method should be called if the state of
75+
* a singleton bean has been modified, potentially affecting future
76+
* interaction with the context.
77+
* <p>In addition, the semantics of the supplied {@code HierarchyMode} must
19278
* be honored. See the Javadoc for {@link HierarchyMode} for details.
19379
* @param key the context key; never {@code null}
19480
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
19581
* is not part of a hierarchy
19682
*/
197-
public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
198-
Assert.notNull(key, "Key must not be null");
83+
void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode);
19984

200-
// startKey is the level at which to begin clearing the cache, depending
201-
// on the configured hierarchy mode.
202-
MergedContextConfiguration startKey = key;
203-
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
204-
while (startKey.getParent() != null) {
205-
startKey = startKey.getParent();
206-
}
207-
}
208-
209-
List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
210-
remove(removedContexts, startKey);
211-
212-
// Remove all remaining references to any removed contexts from the
213-
// hierarchy map.
214-
for (MergedContextConfiguration currentKey : removedContexts) {
215-
for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
216-
children.remove(currentKey);
217-
}
218-
}
85+
/**
86+
* Determine the number of contexts currently stored in the cache.
87+
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
88+
* this method must return {@code Integer.MAX_VALUE}.
89+
*/
90+
int size();
21991

220-
// Remove empty entries from the hierarchy map.
221-
for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
222-
if (this.hierarchyMap.get(currentKey).isEmpty()) {
223-
this.hierarchyMap.remove(currentKey);
224-
}
225-
}
226-
}
92+
/**
93+
* Determine the number of parent contexts currently tracked within the cache.
94+
*/
95+
int getParentContextCount();
22796

228-
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
229-
Assert.notNull(key, "Key must not be null");
97+
/**
98+
* Get the overall hit count for this cache.
99+
* <p>A <em>hit</em> is any access to the cache that returns a non-null
100+
* context for the queried key.
101+
*/
102+
int getHitCount();
230103

231-
Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
232-
if (children != null) {
233-
for (MergedContextConfiguration child : children) {
234-
// Recurse through lower levels
235-
remove(removedContexts, child);
236-
}
237-
// Remove the set of children for the current context from the hierarchy map.
238-
this.hierarchyMap.remove(key);
239-
}
104+
/**
105+
* Get the overall miss count for this cache.
106+
* <p>A <em>miss</em> is any access to the cache that returns a {@code null}
107+
* context for the queried key.
108+
*/
109+
int getMissCount();
240110

241-
// Physically remove and close leaf nodes first (i.e., on the way back up the
242-
// stack as opposed to prior to the recursive call).
243-
ApplicationContext context = this.contextMap.remove(key);
244-
if (context instanceof ConfigurableApplicationContext) {
245-
((ConfigurableApplicationContext) context).close();
246-
}
247-
removedContexts.add(key);
248-
}
111+
/**
112+
* Reset all state maintained by this cache including statistics.
113+
* @see #clear()
114+
* @see #clearStatistics()
115+
*/
116+
void reset();
249117

250118
/**
251-
* Determine the number of contexts currently stored in the cache.
252-
* <p>If the cache contains more than {@code Integer.MAX_VALUE} elements,
253-
* this method returns {@code Integer.MAX_VALUE}.
119+
* Clear all contexts from the cache, clearing context hierarchy information as well.
254120
*/
255-
public int size() {
256-
return this.contextMap.size();
257-
}
121+
void clear();
258122

259123
/**
260-
* Determine the number of parent contexts currently tracked within the cache.
124+
* Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
261125
*/
262-
public int getParentContextCount() {
263-
return this.hierarchyMap.size();
264-
}
126+
void clearStatistics();
265127

266128
/**
267-
* Generate a text string containing the statistics for this cache.
268-
* <p>Specifically, the returned string contains the {@linkplain #size},
269-
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount() miss count},
270-
* and {@linkplain #getParentContextCount() parent context count}.
271-
* @return the statistics for this cache
129+
* Generate a text string containing the implementation type of this
130+
* cache and its statistics.
131+
* <p>The value returned by this method will be used primarily for
132+
* logging purposes.
133+
* <p>Specifically, the returned string should contain the name of the
134+
* concrete {@code ContextCache} implementation, the {@linkplain #size},
135+
* {@linkplain #getParentContextCount() parent context count},
136+
* {@linkplain #getHitCount() hit count}, {@linkplain #getMissCount()
137+
* miss count}, and any other information useful in monitoring the
138+
* state of this cache.
139+
* @return a string representation of this cache, including statistics
272140
*/
273141
@Override
274-
public String toString() {
275-
return new ToStringCreator(this)
276-
.append("size", size())
277-
.append("hitCount", getHitCount())
278-
.append("missCount", getMissCount())
279-
.append("parentContextCount", getParentContextCount())
280-
.toString();
281-
}
142+
abstract String toString();
282143

283144
}

0 commit comments

Comments
 (0)