Skip to content

Commit 75ea6f5

Browse files
committed
Revised AbstractCacheManager for consistent locking when caches get added
Issue: SPR-13492
1 parent 1458c7e commit 75ea6f5

File tree

1 file changed

+78
-19
lines changed

1 file changed

+78
-19
lines changed

spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java

Lines changed: 78 additions & 19 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.
@@ -40,20 +40,36 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
4040

4141
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(16);
4242

43-
private Set<String> cacheNames = new LinkedHashSet<String>(16);
43+
private volatile Set<String> cacheNames = Collections.emptySet();
4444

4545

4646
// Early cache initialization on startup
4747

4848
@Override
4949
public void afterPropertiesSet() {
50+
initializeCaches();
51+
}
52+
53+
/**
54+
* Initialize the static configuration of caches.
55+
* <p>Triggered on startup through {@link #afterPropertiesSet()};
56+
* can also be called to re-initialize at runtime.
57+
* @since 4.2.2
58+
* @see #loadCaches()
59+
*/
60+
public void initializeCaches() {
5061
Collection<? extends Cache> caches = loadCaches();
5162

52-
// Preserve the initial order of the cache names
53-
this.cacheMap.clear();
54-
this.cacheNames.clear();
55-
for (Cache cache : caches) {
56-
addCache(cache);
63+
synchronized (this.cacheMap) {
64+
this.cacheNames = Collections.emptySet();
65+
this.cacheMap.clear();
66+
Set<String> cacheNames = new LinkedHashSet<String>(caches.size());
67+
for (Cache cache : caches) {
68+
String name = cache.getName();
69+
this.cacheMap.put(name, decorateCache(cache));
70+
cacheNames.add(name);
71+
}
72+
this.cacheNames = Collections.unmodifiableSet(cacheNames);
5773
}
5874
}
5975

@@ -69,37 +85,79 @@ public void afterPropertiesSet() {
6985

7086
@Override
7187
public Cache getCache(String name) {
72-
Cache cache = lookupCache(name);
88+
Cache cache = this.cacheMap.get(name);
7389
if (cache != null) {
7490
return cache;
7591
}
7692
else {
77-
Cache missingCache = getMissingCache(name);
78-
if (missingCache != null) {
79-
addCache(missingCache);
80-
return lookupCache(name); // may be decorated
93+
// Fully synchronize now for missing cache creation...
94+
synchronized (this.cacheMap) {
95+
cache = this.cacheMap.get(name);
96+
if (cache == null) {
97+
cache = getMissingCache(name);
98+
if (cache != null) {
99+
cache = decorateCache(cache);
100+
this.cacheMap.put(name, cache);
101+
updateCacheNames(name);
102+
}
103+
}
104+
return cache;
81105
}
82-
return null;
83106
}
84107
}
85108

86109
@Override
87110
public Collection<String> getCacheNames() {
88-
return Collections.unmodifiableSet(this.cacheNames);
111+
return this.cacheNames;
89112
}
90113

91114

92-
// Common cache initialization delegates/callbacks
115+
// Common cache initialization delegates for subclasses
116+
117+
/**
118+
* Check for a registered cache of the given name.
119+
* In contrast to {@link #getCache(String)}, this method does not trigger
120+
* the lazy creation of missing caches via {@link #getMissingCache(String)}.
121+
* @param name the cache identifier (must not be {@code null})
122+
* @return the associated Cache instance, or {@code null} if none found
123+
* @since 4.1
124+
* @see #getCache(String)
125+
* @see #getMissingCache(String)
126+
*/
127+
protected final Cache lookupCache(String name) {
128+
return this.cacheMap.get(name);
129+
}
93130

131+
/**
132+
* Dynamically register an additional Cache with this manager.
133+
* @param cache the Cache to register
134+
*/
94135
protected final void addCache(Cache cache) {
95-
this.cacheMap.put(cache.getName(), decorateCache(cache));
96-
this.cacheNames.add(cache.getName());
136+
String name = cache.getName();
137+
synchronized (this.cacheMap) {
138+
if (this.cacheMap.put(name, decorateCache(cache)) == null) {
139+
updateCacheNames(name);
140+
}
141+
}
97142
}
98143

99-
protected final Cache lookupCache(String name) {
100-
return this.cacheMap.get(name);
144+
/**
145+
* Update the exposed {@link #cacheNames} set with the given name.
146+
* <p>This will always be called within a full {@link #cacheMap} lock
147+
* and effectively behaves like a {@code CopyOnWriteArraySet} with
148+
* preserved order but exposed as an unmodifiable reference.
149+
* @param name the name of the cache to be added
150+
*/
151+
private void updateCacheNames(String name) {
152+
Set<String> cacheNames = new LinkedHashSet<String>(this.cacheNames.size() + 1);
153+
cacheNames.addAll(this.cacheNames);
154+
cacheNames.add(name);
155+
this.cacheNames = Collections.unmodifiableSet(cacheNames);
101156
}
102157

158+
159+
// Overridable template methods for cache initialization
160+
103161
/**
104162
* Decorate the given Cache object if necessary.
105163
* @param cache the Cache object to be added to this CacheManager
@@ -120,6 +178,7 @@ protected Cache decorateCache(Cache cache) {
120178
* @param name the name of the cache to retrieve
121179
* @return the missing cache or {@code null} if no such cache exists or could be
122180
* created
181+
* @since 4.1
123182
* @see #getCache(String)
124183
*/
125184
protected Cache getMissingCache(String name) {

0 commit comments

Comments
 (0)