From ccdcad757a9e013797122cf94732aceed638db29 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 20 May 2016 13:54:54 +0200 Subject: [PATCH] Allow template lookup caching to be disabled Extract TemplateAvailabilityProvider caching logic to a new TemplateAvailabilityProviders class and provide property support to disable it. Also update DevToolsPropertyDefaultsPostProcessor to automatically set the property. Fixes gh-5989 --- .../TemplateAvailabilityProviders.java | 178 +++++++++++++++ .../web/DefaultErrorViewResolver.java | 53 +++-- .../web/ErrorMvcAutoConfiguration.java | 22 +- .../TemplateAvailabilityProvidersTests.java | 211 ++++++++++++++++++ .../web/DefaultErrorViewResolverTests.java | 15 +- ...DevToolsPropertyDefaultsPostProcessor.java | 1 + 6 files changed, 436 insertions(+), 44 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProviders.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProvidersTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProviders.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProviders.java new file mode 100644 index 0000000000..d64c747898 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProviders.java @@ -0,0 +1,178 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.template; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.Assert; + +/** + * Collection of {@link TemplateAvailabilityProvider} beans that can be used to check + * which (if any) templating engine supports a given view. Caches responses unless the + * {@code spring.template.provider.cache} property is set to {@code false}. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class TemplateAvailabilityProviders { + + private final List providers; + + private static final int CACHE_LIMIT = 1024; + + private static final TemplateAvailabilityProvider NONE = new NoTemplateAvailabilityProvider(); + + /** + * resolved template views, returning already cached instances without a global lock. + */ + private final Map resolved = new ConcurrentHashMap( + CACHE_LIMIT); + + /** + * Map from view name resolve template view, synchronized when accessed. + */ + @SuppressWarnings("serial") + private final Map cache = new LinkedHashMap( + CACHE_LIMIT, 0.75f, true) { + + @Override + protected boolean removeEldestEntry( + Map.Entry eldest) { + if (size() > CACHE_LIMIT) { + TemplateAvailabilityProviders.this.resolved.remove(eldest.getKey()); + return true; + } + return false; + } + + }; + + /** + * Create a new {@link TemplateAvailabilityProviders} instance. + * @param applicationContext the source application context + */ + public TemplateAvailabilityProviders(ApplicationContext applicationContext) { + this(applicationContext == null ? null : applicationContext.getClassLoader()); + } + + /** + * Create a new {@link TemplateAvailabilityProviders} instance. + * @param classLoader the source class loader + */ + public TemplateAvailabilityProviders(ClassLoader classLoader) { + Assert.notNull(classLoader, "ClassLoader must not be null"); + this.providers = SpringFactoriesLoader + .loadFactories(TemplateAvailabilityProvider.class, classLoader); + } + + /** + * Create a new {@link TemplateAvailabilityProviders} instance. + * @param providers the underlying providers + */ + protected TemplateAvailabilityProviders( + Collection providers) { + Assert.notNull(providers, "Providers must not be null"); + this.providers = new ArrayList(providers); + } + + /** + * Return the underlying providers being used. + * @return the providers being used + */ + public List getProviders() { + return this.providers; + } + + /** + * Get the provider that can be used to render the given view. + * @param view the view to render + * @param applicationContext the application context + * @return a {@link TemplateAvailabilityProvider} or null + */ + public TemplateAvailabilityProvider getProvider(String view, + ApplicationContext applicationContext) { + Assert.notNull(applicationContext, "ApplicationContext must not be null"); + return getProvider(view, applicationContext.getEnvironment(), + applicationContext.getClassLoader(), applicationContext); + } + + /** + * Get the provider that can be used to render the given view. + * @param view the view to render + * @param environment the environment + * @param classLoader the class loader + * @param resourceLoader the resource loader + * @return a {@link TemplateAvailabilityProvider} or null + */ + public TemplateAvailabilityProvider getProvider(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader) { + Assert.notNull(view, "View must not be null"); + Assert.notNull(environment, "Environment must not be null"); + Assert.notNull(classLoader, "ClassLoader must not be null"); + Assert.notNull(resourceLoader, "ResourceLoader must not be null"); + + RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver( + environment, "spring.template.provider."); + if (!propertyResolver.getProperty("cache", Boolean.class, true)) { + return findProvider(view, environment, classLoader, resourceLoader); + } + TemplateAvailabilityProvider provider = this.resolved.get(view); + if (provider == null) { + synchronized (this.cache) { + provider = findProvider(view, environment, classLoader, resourceLoader); + provider = (provider == null ? NONE : provider); + this.resolved.put(view, provider); + this.cache.put(view, provider); + } + } + return (provider == NONE ? null : provider); + } + + private TemplateAvailabilityProvider findProvider(String view, + Environment environment, ClassLoader classLoader, + ResourceLoader resourceLoader) { + for (TemplateAvailabilityProvider candidate : this.providers) { + if (candidate.isTemplateAvailable(view, environment, classLoader, + resourceLoader)) { + return candidate; + } + } + return null; + } + + private static class NoTemplateAvailabilityProvider + implements TemplateAvailabilityProvider { + + @Override + public boolean isTemplateAvailable(String view, Environment environment, + ClassLoader classLoader, ResourceLoader resourceLoader) { + return false; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolver.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolver.java index c8a5568ec1..02e3c2fa55 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolver.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolver.java @@ -18,17 +18,17 @@ package org.springframework.boot.autoconfigure.web; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.io.Resource; -import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus.Series; import org.springframework.http.MediaType; @@ -70,7 +70,7 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private final ResourceProperties resourceProperties; - private final List templateAvailabilityProviders; + private final TemplateAvailabilityProviders templateAvailabilityProviders; private int order = Ordered.LOWEST_PRECEDENCE; @@ -81,19 +81,17 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { */ public DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) { - this(applicationContext, resourceProperties, - loadTemplateAvailabilityProviders(applicationContext)); - } - - private static List loadTemplateAvailabilityProviders( - ApplicationContext applicationContext) { - return SpringFactoriesLoader.loadFactories(TemplateAvailabilityProvider.class, - applicationContext == null ? null : applicationContext.getClassLoader()); + Assert.notNull(applicationContext, "ApplicationContext must not be null"); + Assert.notNull(resourceProperties, "ResourceProperties must not be null"); + this.applicationContext = applicationContext; + this.resourceProperties = resourceProperties; + this.templateAvailabilityProviders = new TemplateAvailabilityProviders( + applicationContext); } DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties, - List templateAvailabilityProviders) { + TemplateAvailabilityProviders templateAvailabilityProviders) { Assert.notNull(applicationContext, "ApplicationContext must not be null"); Assert.notNull(resourceProperties, "ResourceProperties must not be null"); this.applicationContext = applicationContext; @@ -101,6 +99,14 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { this.templateAvailabilityProviders = templateAvailabilityProviders; } + DefaultErrorViewResolver(AnnotationConfigApplicationContext applicationContext, + ResourceProperties resourceProperties, + TemplateAvailabilityProviders templateAvailabilityProviders) { + this.applicationContext = applicationContext; + this.resourceProperties = resourceProperties; + this.templateAvailabilityProviders = templateAvailabilityProviders; + } + @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) { @@ -112,29 +118,20 @@ public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { } private ModelAndView resolve(String viewName, Map model) { - ModelAndView modelAndView = resolveTemplate(viewName, model); - if (modelAndView == null) { - modelAndView = resolveResource(viewName, model); + String errorViewName = "error/" + viewName; + TemplateAvailabilityProvider provider = this.templateAvailabilityProviders + .getProvider(errorViewName, this.applicationContext); + if (provider != null) { + return new ModelAndView(errorViewName, model); } - return modelAndView; - } - - private ModelAndView resolveTemplate(String viewName, Map model) { - for (TemplateAvailabilityProvider templateAvailabilityProvider : this.templateAvailabilityProviders) { - if (templateAvailabilityProvider.isTemplateAvailable("error/" + viewName, - this.applicationContext.getEnvironment(), - this.applicationContext.getClassLoader(), this.applicationContext)) { - return new ModelAndView("error/" + viewName, model); - } - } - return null; + return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map model) { for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); - resource = resource.createRelative("error/" + viewName + ".html"); + resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java index c8e31dc748..a89538e0e6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java @@ -41,6 +41,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.ErrorPage; @@ -53,7 +54,6 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.expression.MapAccessor; import org.springframework.core.Ordered; -import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; @@ -166,19 +166,15 @@ public class ErrorMvcAutoConfiguration { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - List availabilityProviders = SpringFactoriesLoader - .loadFactories(TemplateAvailabilityProvider.class, - context.getClassLoader()); - - for (TemplateAvailabilityProvider availabilityProvider : availabilityProviders) { - if (availabilityProvider.isTemplateAvailable("error", - context.getEnvironment(), context.getClassLoader(), - context.getResourceLoader())) { - return ConditionOutcome.noMatch("Template from " - + availabilityProvider + " found for error view"); - } + TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders( + context.getClassLoader()); + TemplateAvailabilityProvider provider = providers.getProvider("error", + context.getEnvironment(), context.getClassLoader(), + context.getResourceLoader()); + if (provider != null) { + return ConditionOutcome + .noMatch("Template from " + provider + " found for error view"); } - return ConditionOutcome.match("No error template view detected"); } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProvidersTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProvidersTests.java new file mode 100644 index 0000000000..ea38f5da25 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/template/TemplateAvailabilityProvidersTests.java @@ -0,0 +1,211 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.template; + +import java.util.Collection; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link TemplateAvailabilityProviders}. + * + * @author Phillip Webb + */ +public class TemplateAvailabilityProvidersTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private TemplateAvailabilityProviders providers; + + @Mock + private TemplateAvailabilityProvider provider; + + private String view = "view"; + + private ClassLoader classLoader = getClass().getClassLoader(); + + private MockEnvironment environment = new MockEnvironment(); + + @Mock + private ResourceLoader resourceLoader; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.providers = new TemplateAvailabilityProviders( + Collections.singleton(this.provider)); + } + + @Test + public void createWhenApplicationContextIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ClassLoader must not be null"); + new TemplateAvailabilityProviders((ApplicationContext) null); + } + + @Test + public void createWhenUsingApplicationContextShouldLoadProviders() throws Exception { + ApplicationContext applicationContext = mock(ApplicationContext.class); + given(applicationContext.getClassLoader()).willReturn(this.classLoader); + TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders( + applicationContext); + assertThat(providers.getProviders()).isNotEmpty(); + verify(applicationContext).getClassLoader(); + } + + @Test + public void createWhenClassLoaderIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ClassLoader must not be null"); + new TemplateAvailabilityProviders((ClassLoader) null); + } + + @Test + public void createWhenUsingClassLoaderShouldLoadProviders() throws Exception { + TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders( + this.classLoader); + assertThat(providers.getProviders()).isNotEmpty(); + } + + @Test + public void createWhenProvidersIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Providers must not be null"); + new TemplateAvailabilityProviders( + (Collection) null); + } + + @Test + public void createWhenUsingProvidersShouldUseProviders() throws Exception { + TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders( + Collections.singleton(this.provider)); + assertThat(providers.getProviders()).containsOnly(this.provider); + } + + @Test + public void getProviderWhenApplicationContextIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ApplicationContext must not be null"); + this.providers.getProvider(this.view, null); + } + + @Test + public void getProviderWhenViewIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("View must not be null"); + this.providers.getProvider(null, this.environment, this.classLoader, + this.resourceLoader); + } + + @Test + public void getProviderWhenEnvironmentIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Environment must not be null"); + this.providers.getProvider(this.view, null, this.classLoader, + this.resourceLoader); + } + + @Test + public void getProviderWhenClassLoaderIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ClassLoader must not be null"); + this.providers.getProvider(this.view, this.environment, null, + this.resourceLoader); + } + + @Test + public void getProviderWhenResourceLoaderIsNullShouldThrowException() + throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("ResourceLoader must not be null"); + this.providers.getProvider(this.view, this.environment, this.classLoader, null); + } + + @Test + public void getProviderWhenNoneMatchShouldReturnNull() throws Exception { + TemplateAvailabilityProvider found = this.providers.getProvider(this.view, + this.environment, this.classLoader, this.resourceLoader); + assertThat(found).isNull(); + verify(this.provider).isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader); + } + + @Test + public void getProviderWhenMatchShouldReturnProvider() throws Exception { + given(this.provider.isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader)).willReturn(true); + TemplateAvailabilityProvider found = this.providers.getProvider(this.view, + this.environment, this.classLoader, this.resourceLoader); + assertThat(found).isSameAs(this.provider); + + } + + @Test + public void getProviderShouldCacheMatchResult() throws Exception { + given(this.provider.isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader)).willReturn(true); + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + verify(this.provider, times(1)).isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader); + } + + @Test + public void getProviderShouldCacheNoMatchResult() throws Exception { + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + verify(this.provider, times(1)).isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader); + } + + @Test + public void getProvderWhenCacheDisabledShouldNotUseCache() throws Exception { + given(this.provider.isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader)).willReturn(true); + this.environment.setProperty("spring.template.provider.cache", "false"); + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + this.providers.getProvider(this.view, this.environment, this.classLoader, + this.resourceLoader); + verify(this.provider, times(2)).isTemplateAvailable(this.view, this.environment, + this.classLoader, this.resourceLoader); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolverTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolverTests.java index 2be36b5387..604f668ac4 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolverTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DefaultErrorViewResolverTests.java @@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.web; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -31,6 +30,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider; +import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.core.Ordered; @@ -78,8 +78,8 @@ public class DefaultErrorViewResolverTests { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.refresh(); this.resourceProperties = new ResourceProperties(); - List templateAvailabilityProviders = Collections - .singletonList(this.templateAvailabilityProvider); + TemplateAvailabilityProviders templateAvailabilityProviders = new TestTemplateAvailabilityProviders( + this.templateAvailabilityProvider); this.resolver = new DefaultErrorViewResolver(applicationContext, this.resourceProperties, templateAvailabilityProviders); } @@ -221,4 +221,13 @@ public class DefaultErrorViewResolverTests { return response; } + private static class TestTemplateAvailabilityProviders + extends TemplateAvailabilityProviders { + + TestTemplateAvailabilityProviders(TemplateAvailabilityProvider provider) { + super(Collections.singletonList(provider)); + } + + } + } diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java index 6ea1be8ae2..5683aa65ba 100755 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java @@ -51,6 +51,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro properties.put("server.session.persistent", "true"); properties.put("spring.h2.console.enabled", "true"); properties.put("spring.resources.cache-period", "0"); + properties.put("spring.template.provider.cache", "false"); PROPERTIES = Collections.unmodifiableMap(properties); }