Cache resolved error template view names

Fixes gh-5933
pull/5972/head
Phillip Webb 9 years ago
parent 29898c73d3
commit c15c146021

@ -18,8 +18,10 @@ package org.springframework.boot.autoconfigure.web;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -66,6 +68,10 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
SERIES_VIEWS = Collections.unmodifiableMap(views); SERIES_VIEWS = Collections.unmodifiableMap(views);
} }
private static final int CACHE_LIMIT = 1024;
private static final Object UNRESOLVED = new Object();
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties; private final ResourceProperties resourceProperties;
@ -74,6 +80,30 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE; private int order = Ordered.LOWEST_PRECEDENCE;
/**
* resolved template views, returning already cached instances without a global lock.
*/
private final Map<Object, Object> resolved = new ConcurrentHashMap<Object, Object>(
CACHE_LIMIT);
/**
* Map from view name resolve template view, synchronized when accessed.
*/
@SuppressWarnings("serial")
private final Map<Object, Object> cache = new LinkedHashMap<Object, Object>(
CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
if (size() > CACHE_LIMIT) {
DefaultErrorViewResolver.this.resolved.remove(eldest.getKey());
return true;
}
return false;
}
};
/** /**
* Create a new {@link DefaultErrorViewResolver} instance. * Create a new {@link DefaultErrorViewResolver} instance.
* @param applicationContext the source application context * @param applicationContext the source application context
@ -120,11 +150,25 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
} }
private ModelAndView resolveTemplate(String viewName, Map<String, Object> model) { private ModelAndView resolveTemplate(String viewName, Map<String, Object> model) {
Object resolved = this.resolved.get(viewName);
if (resolved == null) {
synchronized (this.cache) {
resolved = resolveTemplateViewName(viewName);
resolved = (resolved == null ? UNRESOLVED : resolved);
this.resolved.put(viewName, resolved);
this.cache.put(viewName, resolved);
}
}
return (resolved == UNRESOLVED ? null
: new ModelAndView((String) resolved, model));
}
private String resolveTemplateViewName(String viewName) {
for (TemplateAvailabilityProvider templateAvailabilityProvider : this.templateAvailabilityProviders) { for (TemplateAvailabilityProvider templateAvailabilityProvider : this.templateAvailabilityProviders) {
if (templateAvailabilityProvider.isTemplateAvailable("error/" + viewName, if (templateAvailabilityProvider.isTemplateAvailable("error/" + viewName,
this.applicationContext.getEnvironment(), this.applicationContext.getEnvironment(),
this.applicationContext.getClassLoader(), this.applicationContext)) { this.applicationContext.getClassLoader(), this.applicationContext)) {
return new ModelAndView("error/" + viewName, model); return "error/" + viewName;
} }
} }
return null; return null;

@ -47,6 +47,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -198,6 +199,21 @@ public class DefaultErrorViewResolverTests {
assertThat(response.getContentType()).isEqualTo(MediaType.TEXT_HTML_VALUE); assertThat(response.getContentType()).isEqualTo(MediaType.TEXT_HTML_VALUE);
} }
@Test
public void resolveShouldCacheTemplate() throws Exception {
given(this.templateAvailabilityProvider.isTemplateAvailable(eq("error/4xx"),
any(Environment.class), any(ClassLoader.class),
any(ResourceLoader.class))).willReturn(true);
for (int i = 0; i < 10; i++) {
ModelAndView resolved = this.resolver.resolveErrorView(this.request,
HttpStatus.NOT_FOUND, this.model);
assertThat(resolved.getViewName()).isEqualTo("error/4xx");
}
verify(this.templateAvailabilityProvider, times(1)).isTemplateAvailable(
eq("error/4xx"), any(Environment.class), any(ClassLoader.class),
any(ResourceLoader.class));
}
@Test @Test
public void orderShouldBeLowest() throws Exception { public void orderShouldBeLowest() throws Exception {
assertThat(this.resolver.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE); assertThat(this.resolver.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE);

Loading…
Cancel
Save