Add Thymeleaf auto-configuration for WebFlux
Thymeleaf 3.0 implements the Spring 5.0 view infrastructure for WebMVC and the new WebFlux framework. This commit adds auto-configuration for the WebFlux support. In that process, the configuration property for `spring.thymeleaf` has been changed to add `spring.thymeleaf.servlet` and `spring.thymeleaf.reactive` for MVC/WebFlux specific properties. Now that the `spring-boot-starter-thymeleaf` does not only support Spring MVC, the transitive dependency on `spring-boot-starter-web` is removed from it. Fixes gh-8124
This commit is contained in:
@@ -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<String, String> 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<ITemplateResolver> 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<String, String> 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<ITemplateResolver> templateResolvers;
|
||||
|
||||
private final Collection<IDialect> dialects;
|
||||
|
||||
|
||||
ThymeleafReactiveConfiguration(Collection<ITemplateResolver> templateResolvers,
|
||||
ObjectProvider<Collection<IDialect>> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<MediaType> mediaTypes =
|
||||
new ArrayList(Collections.singletonList(MediaType.TEXT_HTML));
|
||||
|
||||
public List<MediaType> getMediaTypes() {
|
||||
return this.mediaTypes;
|
||||
}
|
||||
|
||||
public void setMediaTypes(List<MediaType> mediaTypes) {
|
||||
this.mediaTypes = mediaTypes;
|
||||
}
|
||||
|
||||
public int getMaxChunkSize() {
|
||||
return this.maxChunkSize;
|
||||
}
|
||||
|
||||
public void setMaxChunkSize(int maxChunkSize) {
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("<html>bar</html>");
|
||||
}
|
||||
|
||||
@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("<html><body data-foo=\"bar\"></body></html>");
|
||||
}
|
||||
|
||||
@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("<html><body>2015-11-24</body></html>");
|
||||
}
|
||||
|
||||
@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("<html><body>bar</body></html>");
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf-spring5</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user