ContextCache.java 9.2 KB
Newer Older
A
Arjen Poutsma 已提交
1
/*
2
 * Copyright 2002-2015 the original author or authors.
A
Arjen Poutsma 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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;

19 20 21
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
A
Arjen Poutsma 已提交
22
import java.util.Map;
23
import java.util.Set;
A
Arjen Poutsma 已提交
24
import java.util.concurrent.ConcurrentHashMap;
25
import java.util.concurrent.atomic.AtomicInteger;
A
Arjen Poutsma 已提交
26 27 28 29

import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
30
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
A
Arjen Poutsma 已提交
31 32 33
import org.springframework.util.Assert;

/**
34 35
 * Cache for Spring {@link ApplicationContext ApplicationContexts} in a test
 * environment.
A
Arjen Poutsma 已提交
36
 *
37 38
 * <p>{@code ContextCache} maintains a cache of {@code ApplicationContexts}
 * keyed by {@link MergedContextConfiguration} instances.
39
 *
40 41 42 43 44 45
 * <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.
A
Arjen Poutsma 已提交
46 47 48 49 50 51 52
 *
 * @author Sam Brannen
 * @author Juergen Hoeller
 * @since 2.5
 */
class ContextCache {

53 54 55
	/**
	 * Map of context keys to Spring {@code ApplicationContext} instances.
	 */
56 57
	private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
			new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
58

A
Arjen Poutsma 已提交
59
	/**
60 61 62 63
	 * 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.
A
Arjen Poutsma 已提交
64
	 */
65 66
	private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
			new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
A
Arjen Poutsma 已提交
67

68
	private final AtomicInteger hitCount = new AtomicInteger();
A
Arjen Poutsma 已提交
69

70
	private final AtomicInteger missCount = new AtomicInteger();
A
Arjen Poutsma 已提交
71

72 73 74 75 76 77 78 79 80 81 82
	/**
	 * Reset all state maintained by this cache.
	 * @see #clear()
	 * @see #clearStatistics()
	 */
	public void reset() {
		synchronized (contextMap) {
			clear();
			clearStatistics();
		}
	}
A
Arjen Poutsma 已提交
83 84

	/**
S
Sam Brannen 已提交
85
	 * Clear all contexts from the cache and clear context hierarchy information as well.
A
Arjen Poutsma 已提交
86
	 */
87
	public void clear() {
88 89 90 91
		synchronized (contextMap) {
			this.contextMap.clear();
			this.hierarchyMap.clear();
		}
A
Arjen Poutsma 已提交
92 93 94
	}

	/**
S
Sam Brannen 已提交
95
	 * Clear hit and miss count statistics for the cache (i.e., reset counters to zero).
A
Arjen Poutsma 已提交
96
	 */
97
	public void clearStatistics() {
98 99 100 101
		synchronized (contextMap) {
			this.hitCount.set(0);
			this.missCount.set(0);
		}
A
Arjen Poutsma 已提交
102 103 104
	}

	/**
S
Sam Brannen 已提交
105
	 * Determine whether there is a cached context for the given key.
106
	 * @param key the context key (never {@code null})
S
Sam Brannen 已提交
107
	 * @return {@code true} if the cache contains a context with the given key
A
Arjen Poutsma 已提交
108
	 */
109
	public boolean contains(MergedContextConfiguration key) {
A
Arjen Poutsma 已提交
110
		Assert.notNull(key, "Key must not be null");
111
		return this.contextMap.containsKey(key);
A
Arjen Poutsma 已提交
112 113 114
	}

	/**
115
	 * Obtain a cached {@code ApplicationContext} for the given key.
116 117
	 * <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
	 * be updated accordingly.
118
	 * @param key the context key (never {@code null})
119 120
	 * @return the corresponding {@code ApplicationContext} instance, or {@code null}
	 * if not found in the cache
A
Arjen Poutsma 已提交
121 122
	 * @see #remove
	 */
123
	public ApplicationContext get(MergedContextConfiguration key) {
A
Arjen Poutsma 已提交
124
		Assert.notNull(key, "Key must not be null");
125 126 127 128 129 130
		ApplicationContext context = this.contextMap.get(key);
		if (context == null) {
			this.missCount.incrementAndGet();
		}
		else {
			this.hitCount.incrementAndGet();
A
Arjen Poutsma 已提交
131
		}
132
		return context;
A
Arjen Poutsma 已提交
133 134 135
	}

	/**
136
	 * Get the overall hit count for this cache.
137 138
	 * <p>A <em>hit</em> is any access to the cache that returns a non-null
	 * context for the queried key.
A
Arjen Poutsma 已提交
139
	 */
140
	public int getHitCount() {
141
		return this.hitCount.get();
A
Arjen Poutsma 已提交
142 143 144
	}

	/**
145
	 * Get the overall miss count for this cache.
146 147
	 * <p>A <em>miss</em> is any access to the cache that returns a {@code null}
	 * context for the queried key.
A
Arjen Poutsma 已提交
148
	 */
149
	public int getMissCount() {
150
		return this.missCount.get();
A
Arjen Poutsma 已提交
151 152 153
	}

	/**
154 155
	 * Explicitly add an {@code ApplicationContext} instance to the cache
	 * under the given key.
156
	 * @param key the context key (never {@code null})
157
	 * @param context the {@code ApplicationContext} instance (never {@code null})
A
Arjen Poutsma 已提交
158
	 */
159
	public void put(MergedContextConfiguration key, ApplicationContext context) {
A
Arjen Poutsma 已提交
160 161 162
		Assert.notNull(key, "Key must not be null");
		Assert.notNull(context, "ApplicationContext must not be null");

163 164 165 166 167 168 169 170
		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);
171
			}
172 173 174
			list.add(child);
			child = parent;
			parent = child.getParent();
175
		}
A
Arjen Poutsma 已提交
176 177 178
	}

	/**
179 180
	 * Remove the context with the given key from the cache and explicitly
	 * {@linkplain ConfigurableApplicationContext#close() close} it if it is an
181
	 * instance of {@link ConfigurableApplicationContext}.
182 183 184
	 * <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.
185 186 187 188 189
	 * <p>In addition, the semantics of the supplied {@code HierarchyMode} will
	 * 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
A
Arjen Poutsma 已提交
190
	 */
191
	public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
A
Arjen Poutsma 已提交
192
		Assert.notNull(key, "Key must not be null");
193 194 195 196 197 198 199 200 201 202

		// 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();
			}
		}

203 204
		List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
		remove(removedContexts, startKey);
205

206 207 208 209 210
		// 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);
211
			}
212
		}
213

214 215 216 217
		// Remove empty entries from the hierarchy map.
		for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
			if (this.hierarchyMap.get(currentKey).isEmpty()) {
				this.hierarchyMap.remove(currentKey);
218 219 220 221 222 223 224
			}
		}
	}

	private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
		Assert.notNull(key, "Key must not be null");

225 226 227 228 229
		Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
		if (children != null) {
			for (MergedContextConfiguration child : children) {
				// Recurse through lower levels
				remove(removedContexts, child);
230
			}
231 232 233
			// Remove the set of children for the current context from the hierarchy map.
			this.hierarchyMap.remove(key);
		}
234

235 236 237 238 239
		// 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();
A
Arjen Poutsma 已提交
240
		}
241
		removedContexts.add(key);
A
Arjen Poutsma 已提交
242 243 244
	}

	/**
S
Sam Brannen 已提交
245 246 247
	 * 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}.
A
Arjen Poutsma 已提交
248
	 */
249 250
	public int size() {
		return this.contextMap.size();
251 252 253 254 255
	}

	/**
	 * Determine the number of parent contexts currently tracked within the cache.
	 */
256 257
	public int getParentContextCount() {
		return this.hierarchyMap.size();
A
Arjen Poutsma 已提交
258 259 260
	}

	/**
261 262 263 264 265
	 * 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
A
Arjen Poutsma 已提交
266
	 */
267
	@Override
A
Arjen Poutsma 已提交
268
	public String toString() {
269 270 271 272 273 274
		return new ToStringCreator(this)
				.append("size", size())
				.append("hitCount", getHitCount())
				.append("missCount", getMissCount())
				.append("parentContextCount", getParentContextCount())
				.toString();
A
Arjen Poutsma 已提交
275 276 277
	}

}