/*
* Copyright 2002-2018 the original author or authors.
*
* 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.web.servlet.view;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
Convenient base class for ViewResolver
implementations. Caches View
objects once resolved: This means that view resolution won't be a performance problem, no matter how costly initial view retrieval is. Subclasses need to implement the loadView
template method, building the View object for a specific view name and locale.
Author: Rod Johnson, Juergen Hoeller See Also:
/**
* Convenient base class for {@link org.springframework.web.servlet.ViewResolver}
* implementations. Caches {@link org.springframework.web.servlet.View} objects
* once resolved: This means that view resolution won't be a performance problem,
* no matter how costly initial view retrieval is.
*
* <p>Subclasses need to implement the {@link #loadView} template method,
* building the View object for a specific view name and locale.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see #loadView
*/
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
Default maximum number of entries for the view cache: 1024. /** Default maximum number of entries for the view cache: 1024. */
public static final int DEFAULT_CACHE_LIMIT = 1024;
Dummy marker object for unresolved views in the cache Maps. /** Dummy marker object for unresolved views in the cache Maps. */
private static final View UNRESOLVED_VIEW = new View() {
@Override
@Nullable
public String getContentType() {
return null;
}
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
}
};
The maximum number of entries in the cache. /** The maximum number of entries in the cache. */
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
Whether we should refrain from resolving views again if unresolved once. /** Whether we should refrain from resolving views again if unresolved once. */
private boolean cacheUnresolved = true;
Fast access cache for Views, returning already cached instances without a global lock. /** Fast access cache for Views, returning already cached instances without a global lock. */
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);
Map from view key to View instance, synchronized for View creation. /** Map from view key to View instance, synchronized for View creation. */
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
Specify the maximum number of entries for the view cache.
Default is 1024.
/**
* Specify the maximum number of entries for the view cache.
* Default is 1024.
*/
public void setCacheLimit(int cacheLimit) {
this.cacheLimit = cacheLimit;
}
Return the maximum number of entries for the view cache.
/**
* Return the maximum number of entries for the view cache.
*/
public int getCacheLimit() {
return this.cacheLimit;
}
Enable or disable caching.
This is equivalent to setting the "cacheLimit"
property to the default limit (1024) or to 0, respectively.
Default is "true": caching is enabled.
Disable this only for debugging and development.
/**
* Enable or disable caching.
* <p>This is equivalent to setting the {@link #setCacheLimit "cacheLimit"}
* property to the default limit (1024) or to 0, respectively.
* <p>Default is "true": caching is enabled.
* Disable this only for debugging and development.
*/
public void setCache(boolean cache) {
this.cacheLimit = (cache ? DEFAULT_CACHE_LIMIT : 0);
}
Return if caching is enabled.
/**
* Return if caching is enabled.
*/
public boolean isCache() {
return (this.cacheLimit > 0);
}
Whether a view name once resolved to null
should be cached and automatically resolved to null
subsequently. Default is "true": unresolved view names are being cached, as of Spring 3.1. Note that this flag only applies if the general "cache"
flag is kept at its default of "true" as well.
Of specific interest is the ability for some AbstractUrlBasedView implementations (FreeMarker, Tiles) to check if an underlying resource exists via AbstractUrlBasedView.checkResource(Locale)
. With this flag set to "false", an underlying resource that re-appears is noticed and used. With the flag set to "true", one check is made only.
/**
* Whether a view name once resolved to {@code null} should be cached and
* automatically resolved to {@code null} subsequently.
* <p>Default is "true": unresolved view names are being cached, as of Spring 3.1.
* Note that this flag only applies if the general {@link #setCache "cache"}
* flag is kept at its default of "true" as well.
* <p>Of specific interest is the ability for some AbstractUrlBasedView
* implementations (FreeMarker, Tiles) to check if an underlying resource
* exists via {@link AbstractUrlBasedView#checkResource(Locale)}.
* With this flag set to "false", an underlying resource that re-appears
* is noticed and used. With the flag set to "true", one check is made only.
*/
public void setCacheUnresolved(boolean cacheUnresolved) {
this.cacheUnresolved = cacheUnresolved;
}
Return if caching of unresolved views is enabled.
/**
* Return if caching of unresolved views is enabled.
*/
public boolean isCacheUnresolved() {
return this.cacheUnresolved;
}
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
private static String formatKey(Object cacheKey) {
return "View with key [" + cacheKey + "] ";
}
Return the cache key for the given view name and the given locale.
Default is a String consisting of view name and locale suffix.
Can be overridden in subclasses.
Needs to respect the locale in general, as a different locale can
lead to a different view resource.
/**
* Return the cache key for the given view name and the given locale.
* <p>Default is a String consisting of view name and locale suffix.
* Can be overridden in subclasses.
* <p>Needs to respect the locale in general, as a different locale can
* lead to a different view resource.
*/
protected Object getCacheKey(String viewName, Locale locale) {
return viewName + '_' + locale;
}
Provides functionality to clear the cache for a certain view.
This can be handy in case developer are able to modify views
(e.g. FreeMarker templates) at runtime after which you'd need to
clear the cache for the specified view.
Params: - viewName – the view name for which the cached view object
(if any) needs to be removed
- locale – the locale for which the view object should be removed
/**
* Provides functionality to clear the cache for a certain view.
* <p>This can be handy in case developer are able to modify views
* (e.g. FreeMarker templates) at runtime after which you'd need to
* clear the cache for the specified view.
* @param viewName the view name for which the cached view object
* (if any) needs to be removed
* @param locale the locale for which the view object should be removed
*/
public void removeFromCache(String viewName, Locale locale) {
if (!isCache()) {
logger.warn("Caching is OFF (removal not necessary)");
}
else {
Object cacheKey = getCacheKey(viewName, locale);
Object cachedView;
synchronized (this.viewCreationCache) {
this.viewAccessCache.remove(cacheKey);
cachedView = this.viewCreationCache.remove(cacheKey);
}
if (logger.isDebugEnabled()) {
// Some debug output might be useful...
logger.debug(formatKey(cacheKey) +
(cachedView != null ? "cleared from cache" : "not found in the cache"));
}
}
}
Clear the entire view cache, removing all cached view objects.
Subsequent resolve calls will lead to recreation of demanded view objects.
/**
* Clear the entire view cache, removing all cached view objects.
* Subsequent resolve calls will lead to recreation of demanded view objects.
*/
public void clearCache() {
logger.debug("Clearing all views from the cache");
synchronized (this.viewCreationCache) {
this.viewAccessCache.clear();
this.viewCreationCache.clear();
}
}
Create the actual View object.
The default implementation delegates to loadView
. This can be overridden to resolve certain view names in a special fashion, before delegating to the actual loadView
implementation provided by the subclass.
Params: - viewName – the name of the view to retrieve
- locale – the Locale to retrieve the view for
Throws: - Exception – if the view couldn't be resolved
See Also: Returns: the View instance, or null
if not found (optional, to allow for ViewResolver chaining)
/**
* Create the actual View object.
* <p>The default implementation delegates to {@link #loadView}.
* This can be overridden to resolve certain view names in a special fashion,
* before delegating to the actual {@code loadView} implementation
* provided by the subclass.
* @param viewName the name of the view to retrieve
* @param locale the Locale to retrieve the view for
* @return the View instance, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view couldn't be resolved
* @see #loadView
*/
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
Subclasses must implement this method, building a View object
for the specified view. The returned View objects will be
cached by this ViewResolver base class.
Subclasses are not forced to support internationalization:
A subclass that does not may simply ignore the locale parameter.
Params: - viewName – the name of the view to retrieve
- locale – the Locale to retrieve the view for
Throws: - Exception – if the view couldn't be resolved
See Also: Returns: the View instance, or null
if not found (optional, to allow for ViewResolver chaining)
/**
* Subclasses must implement this method, building a View object
* for the specified view. The returned View objects will be
* cached by this ViewResolver base class.
* <p>Subclasses are not forced to support internationalization:
* A subclass that does not may simply ignore the locale parameter.
* @param viewName the name of the view to retrieve
* @param locale the Locale to retrieve the view for
* @return the View instance, or {@code null} if not found
* (optional, to allow for ViewResolver chaining)
* @throws Exception if the view couldn't be resolved
* @see #resolveViewName
*/
@Nullable
protected abstract View loadView(String viewName, Locale locale) throws Exception;
}