1
1
/*
2
- * Copyright 2002-2012 the original author or authors.
2
+ * Copyright 2002-2013 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.
19
19
import java .util .LinkedHashMap ;
20
20
import java .util .Locale ;
21
21
import java .util .Map ;
22
+ import java .util .concurrent .ConcurrentHashMap ;
23
+ import javax .servlet .http .HttpServletRequest ;
24
+ import javax .servlet .http .HttpServletResponse ;
22
25
23
26
import org .springframework .web .context .support .WebApplicationObjectSupport ;
24
27
import org .springframework .web .servlet .View ;
@@ -42,19 +45,38 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu
42
45
/** Default maximum number of entries for the view cache: 1024 */
43
46
public static final int DEFAULT_CACHE_LIMIT = 1024 ;
44
47
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
+ };
45
56
57
+
58
+ /** The maximum number of entries in the cache */
46
59
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT ;
47
60
48
61
/** Whether we should refrain from resolving views again if unresolved once */
49
62
private boolean cacheUnresolved = true ;
50
63
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 */
52
68
@ SuppressWarnings ("serial" )
53
- private final Map <Object , View > viewCache =
69
+ private final Map <Object , View > viewCreationCache =
54
70
new LinkedHashMap <Object , View >(DEFAULT_CACHE_LIMIT , 0.75f , true ) {
55
71
@ Override
56
72
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
+ }
58
80
}
59
81
};
60
82
@@ -122,20 +144,27 @@ public View resolveViewName(String viewName, Locale locale) throws Exception {
122
144
}
123
145
else {
124
146
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
+ }
134
163
}
135
164
}
136
165
}
137
- return view ;
138
166
}
167
+ return (view != UNRESOLVED_VIEW ? view : null );
139
168
}
140
169
}
141
170
@@ -166,17 +195,16 @@ public void removeFromCache(String viewName, Locale locale) {
166
195
else {
167
196
Object cacheKey = getCacheKey (viewName , locale );
168
197
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 );
171
201
}
172
- if (cachedView == null ) {
202
+ if (logger . isDebugEnabled () ) {
173
203
// Some debug output might be useful...
174
- if (logger . isDebugEnabled () ) {
204
+ if (cachedView == null ) {
175
205
logger .debug ("No cached instance for view '" + cacheKey + "' was found" );
176
206
}
177
- }
178
- else {
179
- if (logger .isDebugEnabled ()) {
207
+ else {
180
208
logger .debug ("Cache for view " + cacheKey + " has been cleared" );
181
209
}
182
210
}
@@ -189,8 +217,9 @@ public void removeFromCache(String viewName, Locale locale) {
189
217
*/
190
218
public void clearCache () {
191
219
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 ();
194
223
}
195
224
}
196
225
0 commit comments