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..9af1f12674 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 @@ -68,6 +68,9 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu /** Fast access cache for Views, returning already cached instances without a global lock. */ private final Map viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT); + /** Filter function which determines if view should be cached. */ + private ViewCacheFilter viewCacheFilter = (viewName, view, locale) -> true; + /** Map from view key to View instance, synchronized for View creation. */ @SuppressWarnings("serial") private final Map viewCreationCache = @@ -134,6 +137,21 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu this.cacheUnresolved = cacheUnresolved; } + /** + * Filter function which determines if view should be cached. + * Default behaviour is to cache all views. + */ + public void setViewCacheFilter(ViewCacheFilter cacheFilter) { + this.viewCacheFilter = cacheFilter; + } + + /** + * Return filter function which determines if view should be cached. + */ + public ViewCacheFilter getViewCacheFilter() { + return this.viewCacheFilter; + } + /** * Return if caching of unresolved views is enabled. */ @@ -160,7 +178,7 @@ public abstract class AbstractCachingViewResolver extends WebApplicationObjectSu if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } - if (view != null) { + if (view != null && this.viewCacheFilter.filter(viewName, view, locale)) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewCacheFilter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewCacheFilter.java new file mode 100644 index 0000000000..ec543ef360 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ViewCacheFilter.java @@ -0,0 +1,32 @@ +/* + * 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.Locale; + +import org.springframework.web.servlet.View; + +/** + * Filter which determines if view should be cached in {@link AbstractCachingViewResolver}. + * + * @author Sergey Galkin + */ +@FunctionalInterface +public interface ViewCacheFilter { + + boolean filter(String viewName, View view, Locale locale); +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverCacheFilterTest.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverCacheFilterTest.java new file mode 100644 index 0000000000..321ff6353f --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/ViewResolverCacheFilterTest.java @@ -0,0 +1,86 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Locale; +import org.junit.Test; +import org.springframework.web.servlet.View; + +public class ViewResolverCacheFilterTest { + + private interface ViewLoader { + View load(String viewName); + } + + private static class TestViewResolver extends AbstractCachingViewResolver { + + private final ViewLoader viewLoader; + + private TestViewResolver(ViewLoader viewLoader) { + this.viewLoader = viewLoader; + } + + @Override + protected View loadView(String viewName, Locale locale) { + return viewLoader.load(viewName); + } + } + + private final static String VIEW_NAME = "name"; + private final ViewLoader viewLoader = mock(ViewLoader.class); + private final TestViewResolver resolver = new TestViewResolver(viewLoader); + + @Test + public void viewWillBePlacedInCache() throws Exception { + resolver.setViewCacheFilter((n, v, l) -> true); + + resolver.resolveViewName(VIEW_NAME, Locale.ENGLISH); + resolver.resolveViewName(VIEW_NAME, Locale.ENGLISH); + + verify(viewLoader, times(1)).load(any()); + } + + @Test + public void viewWillNotBePlacedInCached() throws Exception { + resolver.setViewCacheFilter((n, v, l) -> false); + + resolver.resolveViewName(VIEW_NAME, Locale.ENGLISH); + resolver.resolveViewName(VIEW_NAME, Locale.ENGLISH); + + verify(viewLoader, times(2)).load(any()); + } + + @Test + public void verifyPassedParamsToFilter() throws Exception { + View view = mock(View.class); + when(viewLoader.load(any())).thenReturn(view); + + ViewCacheFilter filter = mock(ViewCacheFilter.class); + resolver.setViewCacheFilter(filter); + + resolver.resolveViewName(VIEW_NAME, Locale.ENGLISH); + + verify(filter, times(1)).filter(eq(VIEW_NAME), eq(view), eq(Locale.ENGLISH)); + } +}