diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java index dc9bfc7689..f54f612e61 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractCachingViewResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.web.context.support.WebApplicationObjectSupport; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; @@ -58,6 +59,9 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu } }; + /** Default cache filter that always caches. */ + private static final CacheFilter DEFAULT_CACHE_FILTER = (view, viewName, locale) -> true; + /** The maximum number of entries in the cache. */ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; @@ -65,6 +69,9 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu /** Whether we should refrain from resolving views again if unresolved once. */ private boolean cacheUnresolved = true; + /** Filter function that determines if view should be cached. */ + private CacheFilter cacheFilter = DEFAULT_CACHE_FILTER; + /** Fast access cache for Views, returning already cached instances without a global lock. */ private final Map viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT); @@ -141,6 +148,21 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu return this.cacheUnresolved; } + /** + * Sets the filter that determines if view should be cached. + * Default behaviour is to cache all views. + */ + public void setCacheFilter(CacheFilter cacheFilter) { + Assert.notNull(cacheFilter, "CacheFilter must not be null"); + this.cacheFilter = cacheFilter; + } + + /** + * Return filter function that determines if view should be cached. + */ + public CacheFilter getCacheFilter() { + return this.cacheFilter; + } @Override @Nullable @@ -160,7 +182,7 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } - if (view != null) { + if (view != null && this.cacheFilter.filter(view, viewName, locale)) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } @@ -266,4 +288,26 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu @Nullable protected abstract View loadView(String viewName, Locale locale) throws Exception; + + /** + * Filter that determines if view should be cached. + * + * @author Sergey Galkin + * @author Arjen Poutsma + * @since 5.2 + */ + @FunctionalInterface + public interface CacheFilter { + + /** + * Indicates whether the given view should be cached. The name and + * locale used to resolve the view are also provided. + * @param view the view + * @param viewName the name used to resolve {@code view} + * @param locale the locale used to resolve {@code view} + * @return {@code true} if the view should be cached; {@code false} + * otherwise + */ + boolean filter(View view, String viewName, Locale locale); + } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java index 018fd92612..7a353f5f2e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverTests.java @@ -516,6 +516,47 @@ public class ViewResolverTests { assertThat(count.intValue()).isEqualTo(3); } + @Test + public void cacheFilterEnabled() throws Exception { + AtomicInteger count = new AtomicInteger(); + + // filter is enabled by default + AbstractCachingViewResolver viewResolver = new AbstractCachingViewResolver() { + @Override + protected View loadView(String viewName, Locale locale) { + assertThat(viewName).isEqualTo("view"); + assertThat(locale).isEqualTo(Locale.getDefault()); + count.incrementAndGet(); + return new TestView(); + } + }; + + viewResolver.resolveViewName("view", Locale.getDefault()); + viewResolver.resolveViewName("view", Locale.getDefault()); + + assertThat(count.intValue()).isEqualTo(1); + } + + @Test + public void cacheFilterDisabled() throws Exception { + AtomicInteger count = new AtomicInteger(); + + AbstractCachingViewResolver viewResolver = new AbstractCachingViewResolver() { + @Override + protected View loadView(String viewName, Locale locale) { + count.incrementAndGet(); + return new TestView(); + } + }; + + viewResolver.setCacheFilter((view, viewName, locale) -> false); + + viewResolver.resolveViewName("view", Locale.getDefault()); + viewResolver.resolveViewName("view", Locale.getDefault()); + + assertThat(count.intValue()).isEqualTo(2); + } + public static class TestView extends InternalResourceView {