Skip to content

Commit 06c6cbb

Browse files
committed
AbstractCachingViewResolver does not use global lock for accessing existing View instances anymore
Issue: SPR-3145
1 parent 5b1165b commit 06c6cbb

File tree

1 file changed

+52
-23
lines changed

1 file changed

+52
-23
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 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.
@@ -19,6 +19,9 @@
1919
import java.util.LinkedHashMap;
2020
import java.util.Locale;
2121
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
2225

2326
import org.springframework.web.context.support.WebApplicationObjectSupport;
2427
import org.springframework.web.servlet.View;
@@ -42,19 +45,38 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
4245
/** Default maximum number of entries for the view cache: 1024 */
4346
public static final int DEFAULT_CACHE_LIMIT = 1024;
4447

48+
/** Dummy marker object for unresolved views in the cache Maps */
49+
private static final View UNRESOLVED_VIEW = new View() {
50+
public String getContentType() {
51+
return null;
52+
}
53+
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
54+
}
55+
};
4556

57+
58+
/** The maximum number of entries in the cache */
4659
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
4760

4861
/** Whether we should refrain from resolving views again if unresolved once */
4962
private boolean cacheUnresolved = true;
5063

51-
/** Map from view key to View instance */
64+
/** Fast access cache for Views, returning already cached instances without a global lock */
65+
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
66+
67+
/** Map from view key to View instance, synchronized for View creation */
5268
@SuppressWarnings("serial")
53-
private final Map<Object, View> viewCache =
69+
private final Map<Object, View> viewCreationCache =
5470
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
5571
@Override
5672
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
57-
return size() > getCacheLimit();
73+
if (size() > getCacheLimit()) {
74+
viewAccessCache.remove(eldest.getKey());
75+
return true;
76+
}
77+
else {
78+
return false;
79+
}
5880
}
5981
};
6082

@@ -122,20 +144,27 @@ public View resolveViewName(String viewName, Locale locale) throws Exception {
122144
}
123145
else {
124146
Object cacheKey = getCacheKey(viewName, locale);
125-
synchronized (this.viewCache) {
126-
View view = this.viewCache.get(cacheKey);
127-
if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) {
128-
// Ask the subclass to create the View object.
129-
view = createView(viewName, locale);
130-
if (view != null || this.cacheUnresolved) {
131-
this.viewCache.put(cacheKey, view);
132-
if (logger.isTraceEnabled()) {
133-
logger.trace("Cached view [" + cacheKey + "]");
147+
View view = this.viewAccessCache.get(cacheKey);
148+
if (view == null) {
149+
synchronized (this.viewCreationCache) {
150+
view = this.viewCreationCache.get(cacheKey);
151+
if (view == null) {
152+
// Ask the subclass to create the View object.
153+
view = createView(viewName, locale);
154+
if (view == null && this.cacheUnresolved) {
155+
view = UNRESOLVED_VIEW;
156+
}
157+
if (view != null) {
158+
this.viewAccessCache.put(cacheKey, view);
159+
this.viewCreationCache.put(cacheKey, view);
160+
if (logger.isTraceEnabled()) {
161+
logger.trace("Cached view [" + cacheKey + "]");
162+
}
134163
}
135164
}
136165
}
137-
return view;
138166
}
167+
return (view != UNRESOLVED_VIEW ? view : null);
139168
}
140169
}
141170

@@ -166,17 +195,16 @@ public void removeFromCache(String viewName, Locale locale) {
166195
else {
167196
Object cacheKey = getCacheKey(viewName, locale);
168197
Object cachedView;
169-
synchronized (this.viewCache) {
170-
cachedView = this.viewCache.remove(cacheKey);
198+
synchronized (this.viewCreationCache) {
199+
this.viewAccessCache.remove(cacheKey);
200+
cachedView = this.viewCreationCache.remove(cacheKey);
171201
}
172-
if (cachedView == null) {
202+
if (logger.isDebugEnabled()) {
173203
// Some debug output might be useful...
174-
if (logger.isDebugEnabled()) {
204+
if (cachedView == null) {
175205
logger.debug("No cached instance for view '" + cacheKey + "' was found");
176206
}
177-
}
178-
else {
179-
if (logger.isDebugEnabled()) {
207+
else {
180208
logger.debug("Cache for view " + cacheKey + " has been cleared");
181209
}
182210
}
@@ -189,8 +217,9 @@ public void removeFromCache(String viewName, Locale locale) {
189217
*/
190218
public void clearCache() {
191219
logger.debug("Clearing entire view cache");
192-
synchronized (this.viewCache) {
193-
this.viewCache.clear();
220+
synchronized (this.viewCreationCache) {
221+
this.viewAccessCache.clear();
222+
this.viewCreationCache.clear();
194223
}
195224
}
196225

0 commit comments

Comments
 (0)