diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java index 243e980eb1..f8e252a11b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.LinkedHashMap; import javax.annotation.PostConstruct; -import javax.servlet.Servlet; import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect; import nz.net.ultraq.thymeleaf.LayoutDialect; @@ -29,9 +28,12 @@ import org.apache.commons.logging.LogFactory; import org.thymeleaf.dialect.IDialect; import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect; import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect; +import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine; import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.spring5.SpringWebFluxTemplateEngine; import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; import org.thymeleaf.spring5.view.ThymeleafViewResolver; +import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ITemplateResolver; @@ -45,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.template.TemplateLocation; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -63,11 +66,12 @@ import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter; * @author Stephane Nicoll * @author Brian Clozel * @author Eddú Meléndez + * @author Daniel Fernández */ @Configuration @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass(TemplateMode.class) -@AutoConfigureAfter(WebMvcAutoConfiguration.class) +@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class}) public class ThymeleafAutoConfiguration { @Configuration @@ -123,52 +127,6 @@ public class ThymeleafAutoConfiguration { } @Configuration - @ConditionalOnClass({ Servlet.class }) - @ConditionalOnWebApplication(type = Type.SERVLET) - static class ThymeleafViewResolverConfiguration { - - private final ThymeleafProperties properties; - - private final SpringTemplateEngine templateEngine; - - ThymeleafViewResolverConfiguration(ThymeleafProperties properties, - SpringTemplateEngine templateEngine) { - this.properties = properties; - this.templateEngine = templateEngine; - } - - @Bean - @ConditionalOnMissingBean(name = "thymeleafViewResolver") - @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) - public ThymeleafViewResolver thymeleafViewResolver() { - ThymeleafViewResolver resolver = new ThymeleafViewResolver(); - resolver.setTemplateEngine(this.templateEngine); - resolver.setCharacterEncoding(this.properties.getEncoding().name()); - resolver.setContentType(appendCharset(this.properties.getContentType(), - resolver.getCharacterEncoding())); - resolver.setExcludedViewNames(this.properties.getExcludedViewNames()); - resolver.setViewNames(this.properties.getViewNames()); - // This resolver acts as a fallback resolver (e.g. like a - // InternalResourceViewResolver) so it needs to have low precedence - resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5); - resolver.setCache(this.properties.isCache()); - return resolver; - } - - private String appendCharset(MimeType type, String charset) { - if (type.getCharset() != null) { - return type.toString(); - } - LinkedHashMap parameters = new LinkedHashMap<>(); - parameters.put("charset", charset); - parameters.putAll(type.getParameters()); - return new MimeType(type, parameters).toString(); - } - - } - - @Configuration - @ConditionalOnMissingBean(SpringTemplateEngine.class) protected static class ThymeleafDefaultConfiguration { private final Collection templateResolvers; @@ -183,6 +141,7 @@ public class ThymeleafAutoConfiguration { } @Bean + @ConditionalOnMissingBean(SpringTemplateEngine.class) public SpringTemplateEngine templateEngine() { SpringTemplateEngine engine = new SpringTemplateEngine(); for (ITemplateResolver templateResolver : this.templateResolvers) { @@ -198,6 +157,127 @@ public class ThymeleafAutoConfiguration { } + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) + static class ThymeleafWebMvcConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledResourceChain + public ResourceUrlEncodingFilter resourceUrlEncodingFilter() { + return new ResourceUrlEncodingFilter(); + } + + @Configuration + static class ThymeleafViewResolverConfiguration { + + private final ThymeleafProperties properties; + + private final SpringTemplateEngine templateEngine; + + ThymeleafViewResolverConfiguration(ThymeleafProperties properties, + SpringTemplateEngine templateEngine) { + this.properties = properties; + this.templateEngine = templateEngine; + } + + @Bean + @ConditionalOnMissingBean(name = "thymeleafViewResolver") + public ThymeleafViewResolver thymeleafViewResolver() { + ThymeleafViewResolver resolver = new ThymeleafViewResolver(); + resolver.setTemplateEngine(this.templateEngine); + resolver.setCharacterEncoding(this.properties.getEncoding().name()); + resolver.setContentType(appendCharset(this.properties.getServlet().getContentType(), + resolver.getCharacterEncoding())); + resolver.setExcludedViewNames(this.properties.getExcludedViewNames()); + resolver.setViewNames(this.properties.getViewNames()); + // This resolver acts as a fallback resolver (e.g. like a + // InternalResourceViewResolver) so it needs to have low precedence + resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5); + resolver.setCache(this.properties.isCache()); + return resolver; + } + + private String appendCharset(MimeType type, String charset) { + if (type.getCharset() != null) { + return type.toString(); + } + LinkedHashMap parameters = new LinkedHashMap<>(); + parameters.put("charset", charset); + parameters.putAll(type.getParameters()); + return new MimeType(type, parameters).toString(); + } + + } + + } + + @Configuration + @ConditionalOnWebApplication(type = Type.REACTIVE) + @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) + static class ThymeleafReactiveConfiguration { + + private final Collection templateResolvers; + + private final Collection dialects; + + + ThymeleafReactiveConfiguration(Collection templateResolvers, + ObjectProvider> dialectsProvider) { + this.templateResolvers = templateResolvers; + this.dialects = dialectsProvider.getIfAvailable(); + } + + @Bean + @ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class) + public SpringWebFluxTemplateEngine templateEngine() { + SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine(); + for (ITemplateResolver templateResolver : this.templateResolvers) { + engine.addTemplateResolver(templateResolver); + } + if (!CollectionUtils.isEmpty(this.dialects)) { + for (IDialect dialect : this.dialects) { + engine.addDialect(dialect); + } + } + return engine; + } + } + + @Configuration + @ConditionalOnWebApplication(type = Type.REACTIVE) + @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) + static class ThymeleafWebFluxConfiguration { + + private final ThymeleafProperties properties; + + + ThymeleafWebFluxConfiguration(ThymeleafProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver") + public ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine) { + + ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver(); + resolver.setTemplateEngine(templateEngine); + resolver.setDefaultCharset(this.properties.getEncoding()); + resolver.setSupportedMediaTypes(this.properties.getReactive().getMediaTypes()); + resolver.setExcludedViewNames(this.properties.getExcludedViewNames()); + resolver.setViewNames(this.properties.getViewNames()); + if (this.properties.getReactive().getMaxChunkSize() > 0) { + resolver.setResponseMaxChunkSizeBytes(this.properties.getReactive().getMaxChunkSize()); + } + // This resolver acts as a fallback resolver (e.g. like a + // InternalResourceViewResolver) so it needs to have low precedence + resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5); + return resolver; + } + + } + @Configuration @ConditionalOnClass(name = "nz.net.ultraq.thymeleaf.LayoutDialect") protected static class ThymeleafWebLayoutConfiguration { @@ -223,7 +303,7 @@ public class ThymeleafAutoConfiguration { } @Configuration - @ConditionalOnClass({ SpringSecurityDialect.class }) + @ConditionalOnClass({SpringSecurityDialect.class}) protected static class ThymeleafSecurityDialectConfiguration { @Bean @@ -246,17 +326,4 @@ public class ThymeleafAutoConfiguration { } - @Configuration - @ConditionalOnWebApplication(type = Type.SERVLET) - protected static class ThymeleafResourceHandlingConfig { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledResourceChain - public ResourceUrlEncodingFilter resourceUrlEncodingFilter() { - return new ResourceUrlEncodingFilter(); - } - - } - } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java index 4c652b56d9..0afe50609d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafProperties.java @@ -17,14 +17,20 @@ package org.springframework.boot.autoconfigure.thymeleaf; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.MediaType; import org.springframework.util.MimeType; /** * Properties for Thymeleaf. * * @author Stephane Nicoll + * @author Brian Clozel + * @author Daniel Fernández * @since 1.2.0 */ @ConfigurationProperties(prefix = "spring.thymeleaf") @@ -32,8 +38,6 @@ public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8"); - private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html"); - public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; @@ -65,15 +69,10 @@ public class ThymeleafProperties { private String mode = "HTML"; /** - * Template encoding. + * Template files encoding. */ private Charset encoding = DEFAULT_ENCODING; - /** - * Content-Type value. - */ - private MimeType contentType = DEFAULT_CONTENT_TYPE; - /** * Enable template caching. */ @@ -97,10 +96,14 @@ public class ThymeleafProperties { private String[] excludedViewNames; /** - * Enable MVC Thymeleaf view resolution. + * Enable Thymeleaf view resolution for Web frameworks. */ private boolean enabled = true; + private final Servlet servlet = new Servlet(); + + private final Reactive reactive = new Reactive(); + public boolean isEnabled() { return this.enabled; } @@ -157,14 +160,6 @@ public class ThymeleafProperties { this.encoding = encoding; } - public MimeType getContentType() { - return this.contentType; - } - - public void setContentType(MimeType contentType) { - this.contentType = contentType; - } - public boolean isCache() { return this.cache; } @@ -197,4 +192,58 @@ public class ThymeleafProperties { this.viewNames = viewNames; } + public Reactive getReactive() { + return this.reactive; + } + + public Servlet getServlet() { + return this.servlet; + } + + public static class Servlet { + + /** + * Content-Type value written to HTTP responses. + */ + private MimeType contentType = MimeType.valueOf("text/html"); + + public MimeType getContentType() { + return this.contentType; + } + + public void setContentType(MimeType contentType) { + this.contentType = contentType; + } + + } + + public static class Reactive { + + /** + * Maximum size of data buffers used for writing to the response, in bytes. + */ + private int maxChunkSize; + + /** + * Media types supported by the view technology. + */ + private List mediaTypes = + new ArrayList(Collections.singletonList(MediaType.TEXT_HTML)); + + public List getMediaTypes() { + return this.mediaTypes; + } + + public void setMediaTypes(List mediaTypes) { + this.mediaTypes = mediaTypes; + } + + public int getMaxChunkSize() { + return this.maxChunkSize; + } + + public void setMaxChunkSize(int maxChunkSize) { + this.maxChunkSize = maxChunkSize; + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java new file mode 100644 index 0000000000..aba1ef49f6 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafReactiveAutoConfigurationTests.java @@ -0,0 +1,186 @@ +/* + * Copyright 2012-2017 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.thymeleaf; + +import java.io.File; +import java.util.Collections; +import java.util.Locale; + +import nz.net.ultraq.thymeleaf.LayoutDialect; +import nz.net.ultraq.thymeleaf.decorators.strategies.GroupingStrategy; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine; +import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver; +import org.thymeleaf.templateresolver.ITemplateResolver; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +/** + * Tests for {@link ThymeleafAutoConfiguration} in Reactive applications. + * + * @author Brian Clozel + */ +public class ThymeleafReactiveAutoConfigurationTests { + + @Rule + public OutputCapture output = new OutputCapture(); + + private GenericReactiveWebApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void createFromConfigClass() throws Exception { + load(BaseConfiguration.class, "spring.thymeleaf.suffix:.txt"); + TemplateEngine engine = this.context.getBean(TemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); + String result = engine.process("template", attrs); + assertThat(result).isEqualTo("bar"); + } + + @Test + public void overrideCharacterEncoding() throws Exception { + load(BaseConfiguration.class, "spring.thymeleaf.encoding:UTF-16"); + ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); + assertThat(resolver instanceof SpringResourceTemplateResolver).isTrue(); + assertThat(((SpringResourceTemplateResolver) resolver).getCharacterEncoding()) + .isEqualTo("UTF-16"); + ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); + assertThat(views.getDefaultCharset().name()).isEqualTo("UTF-16"); + } + + @Test + public void overrideMediaTypes() throws Exception { + load(BaseConfiguration.class, + "spring.thymeleaf.reactive.media-types:text/html,text/plain"); + ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); + assertThat(views.getSupportedMediaTypes()).contains(MediaType.TEXT_HTML, MediaType.TEXT_PLAIN); + } + + @Test + public void overrideTemplateResolverOrder() throws Exception { + load(BaseConfiguration.class, "spring.thymeleaf.templateResolverOrder:25"); + ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); + assertThat(resolver.getOrder()).isEqualTo(Integer.valueOf(25)); + } + + @Test + public void overrideViewNames() throws Exception { + load(BaseConfiguration.class, "spring.thymeleaf.viewNames:foo,bar"); + ThymeleafReactiveViewResolver views = this.context.getBean(ThymeleafReactiveViewResolver.class); + assertThat(views.getViewNames()).isEqualTo(new String[] {"foo", "bar"}); + } + + @Test + public void templateLocationDoesNotExist() throws Exception { + load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/no-such-directory/"); + this.output.expect(containsString("Cannot find template location")); + } + + @Test + public void templateLocationEmpty() throws Exception { + new File("target/test-classes/templates/empty-directory").mkdir(); + load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/templates/empty-directory/"); + this.output.expect(not(containsString("Cannot find template location"))); + } + + @Test + public void useDataDialect() throws Exception { + load(BaseConfiguration.class); + ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); + String result = engine.process("data-dialect", attrs); + assertThat(result).isEqualTo(""); + } + + @Test + public void useJava8TimeDialect() throws Exception { + load(BaseConfiguration.class); + ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); + Context attrs = new Context(Locale.UK); + String result = engine.process("java8time-dialect", attrs); + assertThat(result).isEqualTo("2015-11-24"); + } + + @Test + public void renderTemplate() throws Exception { + load(BaseConfiguration.class); + ISpringWebFluxTemplateEngine engine = this.context.getBean(ISpringWebFluxTemplateEngine.class); + Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); + String result = engine.process("home", attrs); + assertThat(result).isEqualTo("bar"); + } + + @Test + public void layoutDialectCanBeCustomized() throws Exception { + load(LayoutDialectConfiguration.class); + LayoutDialect layoutDialect = this.context.getBean(LayoutDialect.class); + assertThat(ReflectionTestUtils.getField(layoutDialect, "sortingStrategy")) + .isInstanceOf(GroupingStrategy.class); + } + + private void load(Class config, String... envVariables) { + this.context = new GenericReactiveWebApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, envVariables); + if (config != null) { + this.context.register(config); + } + this.context.register(config); + this.context.refresh(); + } + + @Configuration + @ImportAutoConfiguration({ThymeleafAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class}) + protected static class BaseConfiguration { + + } + + @Configuration + @Import(BaseConfiguration.class) + static class LayoutDialectConfiguration { + + @Bean + public LayoutDialect layoutDialect() { + return new LayoutDialect(new GroupingStrategy()); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafServletAutoConfigurationTests.java similarity index 74% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafServletAutoConfigurationTests.java index b276f43d90..8a0702817b 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/thymeleaf/ThymeleafServletAutoConfigurationTests.java @@ -39,6 +39,7 @@ import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; @@ -52,18 +53,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; /** - * Tests for {@link ThymeleafAutoConfiguration}. + * Tests for {@link ThymeleafAutoConfiguration} in Servlet-based applications. * * @author Dave Syer * @author Stephane Nicoll * @author Eddú Meléndez + * @author Brian Clozel */ -public class ThymeleafAutoConfigurationTests { +public class ThymeleafServletAutoConfigurationTests { @Rule public OutputCapture output = new OutputCapture(); - private AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + private AnnotationConfigWebApplicationContext context; @After public void close() { @@ -74,11 +76,8 @@ public class ThymeleafAutoConfigurationTests { @Test public void createFromConfigClass() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, "spring.thymeleaf.mode:XHTML", + load(BaseConfiguration.class, "spring.thymeleaf.mode:XHTML", "spring.thymeleaf.suffix:"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); TemplateEngine engine = this.context.getBean(TemplateEngine.class); Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); String result = engine.process("template.txt", attrs); @@ -87,11 +86,7 @@ public class ThymeleafAutoConfigurationTests { @Test public void overrideCharacterEncoding() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, - "spring.thymeleaf.encoding:UTF-16"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class, "spring.thymeleaf.encoding:UTF-16"); ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); assertThat(resolver instanceof SpringResourceTemplateResolver).isTrue(); assertThat(((SpringResourceTemplateResolver) resolver).getCharacterEncoding()) @@ -103,44 +98,29 @@ public class ThymeleafAutoConfigurationTests { @Test public void overrideTemplateResolverOrder() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, - "spring.thymeleaf.templateResolverOrder:25"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class, "spring.thymeleaf.templateResolverOrder:25"); ITemplateResolver resolver = this.context.getBean(ITemplateResolver.class); assertThat(resolver.getOrder()).isEqualTo(Integer.valueOf(25)); } @Test public void overrideViewNames() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, - "spring.thymeleaf.viewNames:foo,bar"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class, "spring.thymeleaf.viewNames:foo,bar"); ThymeleafViewResolver views = this.context.getBean(ThymeleafViewResolver.class); - assertThat(views.getViewNames()).isEqualTo(new String[] { "foo", "bar" }); + assertThat(views.getViewNames()).isEqualTo(new String[] {"foo", "bar"}); } @Test public void templateLocationDoesNotExist() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, - "spring.thymeleaf.prefix:classpath:/no-such-directory/"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/no-such-directory/"); this.output.expect(containsString("Cannot find template location")); } @Test public void templateLocationEmpty() throws Exception { new File("target/test-classes/templates/empty-directory").mkdir(); - EnvironmentTestUtils.addEnvironment(this.context, + load(BaseConfiguration.class, "spring.thymeleaf.prefix:classpath:/templates/empty-directory/"); - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); } @Test @@ -165,9 +145,7 @@ public class ThymeleafAutoConfigurationTests { @Test public void useDataDialect() throws Exception { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class); TemplateEngine engine = this.context.getBean(TemplateEngine.class); Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); String result = engine.process("data-dialect", attrs); @@ -176,9 +154,7 @@ public class ThymeleafAutoConfigurationTests { @Test public void useJava8TimeDialect() throws Exception { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class); TemplateEngine engine = this.context.getBean(TemplateEngine.class); Context attrs = new Context(Locale.UK); String result = engine.process("java8time-dialect", attrs); @@ -187,9 +163,7 @@ public class ThymeleafAutoConfigurationTests { @Test public void renderTemplate() throws Exception { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class); TemplateEngine engine = this.context.getBean(TemplateEngine.class); Context attrs = new Context(Locale.UK, Collections.singletonMap("foo", "bar")); String result = engine.process("home", attrs); @@ -216,9 +190,7 @@ public class ThymeleafAutoConfigurationTests { @Test public void registerResourceHandlingFilterDisabledByDefault() throws Exception { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + load(BaseConfiguration.class); assertThat(this.context.getBeansOfType(ResourceUrlEncodingFilter.class)) .isEmpty(); } @@ -226,18 +198,13 @@ public class ThymeleafAutoConfigurationTests { @Test public void registerResourceHandlingFilterOnlyIfResourceChainIsEnabled() throws Exception { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, - "spring.resources.chain.enabled:true"); - this.context.refresh(); + load(BaseConfiguration.class, "spring.resources.chain.enabled:true"); assertThat(this.context.getBean(ResourceUrlEncodingFilter.class)).isNotNull(); } @Test public void layoutDialectCanBeCustomized() throws Exception { - this.context.register(LayoutDialectConfiguration.class); - this.context.refresh(); + load(LayoutDialectConfiguration.class); LayoutDialect layoutDialect = this.context.getBean(LayoutDialect.class); assertThat(ReflectionTestUtils.getField(layoutDialect, "sortingStrategy")) .isInstanceOf(GroupingStrategy.class); @@ -245,19 +212,32 @@ public class ThymeleafAutoConfigurationTests { @Test public void cachingCanBeDisabled() { - this.context.register(ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(this.context, "spring.thymeleaf.cache:false"); - this.context.refresh(); + load(BaseConfiguration.class, "spring.thymeleaf.cache:false"); assertThat(this.context.getBean(ThymeleafViewResolver.class).isCache()).isFalse(); SpringResourceTemplateResolver templateResolver = this.context .getBean(SpringResourceTemplateResolver.class); assertThat(templateResolver.isCacheable()).isFalse(); } + private void load(Class config, String... envVariables) { + this.context = new AnnotationConfigWebApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, envVariables); + if (config != null) { + this.context.register(config); + } + this.context.register(config); + this.context.refresh(); + } + @Configuration - @ImportAutoConfiguration({ ThymeleafAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + @ImportAutoConfiguration({ThymeleafAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class}) + static class BaseConfiguration { + + } + + @Configuration + @Import(BaseConfiguration.class) static class LayoutDialectConfiguration { @Bean diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index eb7d57ae83..692c6883cf 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -437,12 +437,14 @@ content into your application; rather pick only the properties that you need. spring.thymeleaf.cache=true # Enable template caching. spring.thymeleaf.check-template=true # Check that the template exists before rendering it. spring.thymeleaf.check-template-location=true # Check that the templates location exists. - spring.thymeleaf.content-type=text/html # Content-Type value. - spring.thymeleaf.enabled=true # Enable MVC Thymeleaf view resolution. - spring.thymeleaf.encoding=UTF-8 # Template encoding. + spring.thymeleaf.enabled=true # Enable Thymeleaf view resolution for Web frameworks. + spring.thymeleaf.encoding=UTF-8 # Template files encoding. spring.thymeleaf.excluded-view-names= # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.mode=HTML5 # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.prefix=classpath:/templates/ # Prefix that gets prepended to view names when building a URL. + spring.thymeleaf.reactive.max-chunk-size= # Maximum size of data buffers used for writing to the response, in bytes. + spring.thymeleaf.reactive.media-types=text/html # Media types supported by the view technology. + spring.thymeleaf.servlet.content-type=text/html # Content-Type value written to HTTP responses. spring.thymeleaf.suffix=.html # Suffix that gets appended to view names when building a URL. spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved. diff --git a/spring-boot-samples/spring-boot-sample-web-method-security/pom.xml b/spring-boot-samples/spring-boot-sample-web-method-security/pom.xml index d1349ad263..4050412bb2 100644 --- a/spring-boot-samples/spring-boot-sample-web-method-security/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-method-security/pom.xml @@ -28,6 +28,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-boot-samples/spring-boot-sample-web-secure-custom/pom.xml b/spring-boot-samples/spring-boot-sample-web-secure-custom/pom.xml index 2c55f2ed04..789cc85634 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure-custom/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-secure-custom/pom.xml @@ -24,6 +24,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-boot-samples/spring-boot-sample-web-secure-jdbc/pom.xml b/spring-boot-samples/spring-boot-sample-web-secure-jdbc/pom.xml index bc9200c013..0d02baf7b2 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure-jdbc/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-secure-jdbc/pom.xml @@ -24,6 +24,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml index 09ca8418a9..8d9f6aa021 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-secure/pom.xml @@ -28,6 +28,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-boot-samples/spring-boot-sample-web-ui/build.gradle b/spring-boot-samples/spring-boot-sample-web-ui/build.gradle index 67602012a1..b700bf65e4 100644 --- a/spring-boot-samples/spring-boot-sample-web-ui/build.gradle +++ b/spring-boot-samples/spring-boot-sample-web-ui/build.gradle @@ -33,6 +33,7 @@ repositories { } dependencies { + compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-thymeleaf") compile("org.hibernate:hibernate-validator") testCompile("org.springframework.boot:spring-boot-starter-test") diff --git a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml index acd2573d31..d5a3e8ef7a 100755 --- a/spring-boot-samples/spring-boot-sample-web-ui/pom.xml +++ b/spring-boot-samples/spring-boot-sample-web-ui/pom.xml @@ -20,6 +20,10 @@ + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/spring-boot-starters/spring-boot-starter-thymeleaf/pom.xml b/spring-boot-starters/spring-boot-starter-thymeleaf/pom.xml index f851bc98c9..c8faeeab1c 100644 --- a/spring-boot-starters/spring-boot-starter-thymeleaf/pom.xml +++ b/spring-boot-starters/spring-boot-starter-thymeleaf/pom.xml @@ -22,10 +22,6 @@ org.springframework.boot spring-boot-starter - - org.springframework.boot - spring-boot-starter-web - org.thymeleaf thymeleaf-spring5