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. 
1919import  java .util .LinkedHashMap ;
2020import  java .util .Locale ;
2121import  java .util .Map ;
22+ import  java .util .concurrent .ConcurrentHashMap ;
23+ import  javax .servlet .http .HttpServletRequest ;
24+ import  javax .servlet .http .HttpServletResponse ;
2225
2326import  org .springframework .web .context .support .WebApplicationObjectSupport ;
2427import  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