Spring Boot提供了大量的模板引擎,包含了FreeMarker,Groovy,Thymeleaf,Velocity和Mustache,Spring Boot中推荐使用Thymeleaf作为模板引擎,因为Thymeleaf提供了完美的Spring MVC的支持。
Thymeleaf是一个java类库,它是一个xml/xhtml/html5的模板引擎,可以作为MVC的Web应用的View层。
Thymeleaf还提供了额外的模块与Spring MVC集成,所以我们可以使用Thymeleaf完全替代JSP。
在Spring Boot中集成Thymeleaf是通过org.springframework.boot.autoconfigure.thymeleaf包对Thymeleaf进行了自动配置,如下:
通过ThymeleafAutoConfiguration类对集成所需的Bean进行自动配置,包括了templateResolver,templateEngine和thymeleafViewResolver的配置,源代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.thymeleaf;
import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.Servlet;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.conditionalcomments.dialect.ConditionalCommentsDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.resourceresolver.SpringResourceResourceResolver;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
@Configuration
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class})
public class ThymeleafAutoConfiguration {
public ThymeleafAutoConfiguration() {
}
@Configuration
@ConditionalOnWebApplication
protected static class ThymeleafResourceHandlingConfig {
protected ThymeleafResourceHandlingConfig() {
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledResourceChain
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
return new ResourceUrlEncodingFilter();
}
}
@Configuration
@ConditionalOnJava(JavaVersion.EIGHT)
@ConditionalOnClass({Java8TimeDialect.class})
protected static class ThymeleafJava8TimeDialect {
protected ThymeleafJava8TimeDialect() {
}
@Bean
@ConditionalOnMissingBean
public Java8TimeDialect java8TimeDialect() {
return new Java8TimeDialect();
}
}
@Configuration
@ConditionalOnClass({SpringSecurityDialect.class})
protected static class ThymeleafSecurityDialectConfiguration {
protected ThymeleafSecurityDialectConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public SpringSecurityDialect securityDialect() {
return new SpringSecurityDialect();
}
}
@Configuration
@ConditionalOnClass({DataAttributeDialect.class})
protected static class DataAttributeDialectConfiguration {
protected DataAttributeDialectConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public DataAttributeDialect dialect() {
return new DataAttributeDialect();
}
}
@Configuration
@ConditionalOnClass(
name = {"nz.net.ultraq.thymeleaf.LayoutDialect"}
)
protected static class ThymeleafWebLayoutConfiguration {
protected ThymeleafWebLayoutConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public LayoutDialect layoutDialect() {
return new LayoutDialect();
}
}
@Configuration
@ConditionalOnMissingBean({SpringTemplateEngine.class})
protected static class ThymeleafDefaultConfiguration {
private final Collection<ITemplateResolver> templateResolvers;
private final Collection<IDialect> dialects;
public ThymeleafDefaultConfiguration(Collection<ITemplateResolver> templateResolvers, ObjectProvider<Collection<IDialect>> dialectsProvider) {
this.templateResolvers = templateResolvers;
this.dialects = (Collection)dialectsProvider.getIfAvailable();
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
Iterator var2 = this.templateResolvers.iterator();
while(var2.hasNext()) {
ITemplateResolver templateResolver = (ITemplateResolver)var2.next();
engine.addTemplateResolver(templateResolver);
}
if (!CollectionUtils.isEmpty(this.dialects)) {
var2 = this.dialects.iterator();
while(var2.hasNext()) {
IDialect dialect = (IDialect)var2.next();
engine.addDialect(dialect);
}
}
return engine;
}
}
@Configuration
@ConditionalOnClass(
name = {"org.thymeleaf.templatemode.TemplateMode"}
)
static class Thymeleaf3Configuration {
Thymeleaf3Configuration() {
}
@Configuration
@ConditionalOnClass({Servlet.class})
@ConditionalOnWebApplication
static class Thymeleaf3ViewResolverConfiguration extends AbstractThymeleafViewResolverConfiguration {
Thymeleaf3ViewResolverConfiguration(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
super(properties, templateEngine);
}
protected void configureTemplateEngine(ThymeleafViewResolver resolver, SpringTemplateEngine templateEngine) {
Method setTemplateEngine;
try {
setTemplateEngine = ReflectionUtils.findMethod(resolver.getClass(), "setTemplateEngine", new Class[]{Class.forName("org.thymeleaf.ITemplateEngine", true, resolver.getClass().getClassLoader())});
} catch (ClassNotFoundException var5) {
throw new IllegalStateException(var5);
}
ReflectionUtils.invokeMethod(setTemplateEngine, resolver, new Object[]{templateEngine});
}
}
@Configuration
@ConditionalOnMissingBean(
name = {"defaultTemplateResolver"}
)
static class DefaultTemplateResolverConfiguration extends AbstractTemplateResolverConfiguration {
DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
super(properties, applicationContext);
}
@Bean
public SpringResourceTemplateResolver defaultTemplateResolver() {
SpringResourceTemplateResolver resolver = super.defaultTemplateResolver();
Method setCheckExistence = ReflectionUtils.findMethod(resolver.getClass(), "setCheckExistence", new Class[]{Boolean.TYPE});
ReflectionUtils.invokeMethod(setCheckExistence, resolver, new Object[]{this.getProperties().isCheckTemplate()});
return resolver;
}
}
}
@Configuration
@ConditionalOnMissingClass({"org.thymeleaf.templatemode.TemplateMode"})
static class Thymeleaf2Configuration {
Thymeleaf2Configuration() {
}
@Configuration
@ConditionalOnClass({ConditionalCommentsDialect.class})
static class ThymeleafConditionalCommentsDialectConfiguration {
ThymeleafConditionalCommentsDialectConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public ConditionalCommentsDialect conditionalCommentsDialect() {
return new ConditionalCommentsDialect();
}
}
@Configuration
@ConditionalOnClass({Servlet.class})
@ConditionalOnWebApplication
static class Thymeleaf2ViewResolverConfiguration extends AbstractThymeleafViewResolverConfiguration {
Thymeleaf2ViewResolverConfiguration(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
super(properties, templateEngine);
}
protected void configureTemplateEngine(ThymeleafViewResolver resolver, SpringTemplateEngine templateEngine) {
resolver.setTemplateEngine(templateEngine);
}
}
@Configuration
@ConditionalOnMissingBean(
name = {"defaultTemplateResolver"}
)
static class DefaultTemplateResolverConfiguration extends AbstractTemplateResolverConfiguration {
DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
super(properties, applicationContext);
}
@Bean
public SpringResourceResourceResolver thymeleafResourceResolver() {
return new SpringResourceResourceResolver();
}
}
}
}
通过ThymeleafProperties来配置Thymeleaf,在application.yml中以spring.thymeleaf开头来配置,通过查看ThymeleafProperties的主要源代码,我们可以看出如何设置属性以及默认配置:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.thymeleaf;
import java.nio.charset.Charset;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.MimeType;
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
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";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML5";
private Charset encoding;
private MimeType contentType;
private boolean cache;
private Integer templateResolverOrder;
private String[] viewNames;
private String[] excludedViewNames;
private boolean enabled;
public ThymeleafProperties() {
this.encoding = DEFAULT_ENCODING;
this.contentType = DEFAULT_CONTENT_TYPE;
this.cache = true;
this.enabled = true;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isCheckTemplate() {
return this.checkTemplate;
}
public void setCheckTemplate(boolean checkTemplate) {
this.checkTemplate = checkTemplate;
}
public boolean isCheckTemplateLocation() {
return this.checkTemplateLocation;
}
public void setCheckTemplateLocation(boolean checkTemplateLocation) {
this.checkTemplateLocation = checkTemplateLocation;
}
public String getPrefix() {
return this.prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return this.suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getMode() {
return this.mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public Charset getEncoding() {
return this.encoding;
}
public void setEncoding(Charset encoding) {
this.encoding = encoding;
}
public MimeType getContentType() {
return this.contentType;
}
public void setContentType(MimeType contentType) {
this.contentType = contentType;
}
public boolean isCache() {
return this.cache;
}
public void setCache(boolean cache) {
this.cache = cache;
}
public Integer getTemplateResolverOrder() {
return this.templateResolverOrder;
}
public void setTemplateResolverOrder(Integer templateResolverOrder) {
this.templateResolverOrder = templateResolverOrder;
}
public String[] getExcludedViewNames() {
return this.excludedViewNames;
}
public void setExcludedViewNames(String[] excludedViewNames) {
this.excludedViewNames = excludedViewNames;
}
public String[] getViewNames() {
return this.viewNames;
}
public void setViewNames(String[] viewNames) {
this.viewNames = viewNames;
}
}
实战:
1,新建一个spring boot项目,修改pom.xml,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jack</groupId>
<artifactId>springboot2web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot2web</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>-->
<!--thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>1.5.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
上面主要添加了thymeleaf的依赖,然后会自动包含spring-boot-starter-web的依赖
2,示例java bean
此类用来在模板页面展示数据用,包含name属性和age属性:
package com.jack.web.pojo;
/**
* 人的java bean
*/
public class Person {
private String name;
private Integer age;
public Person() {
super();
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3,脚本样式静态文件
根据默认原则,脚本样式,图片等静态文件应放置在src/main/resources/static下,这里引入了Bootstrap和jQuery,如下:
4,演示页面
根据默认原则,页面应该放置在src/main/resources/templates下。在src/main/resources/templates下新建index.html,代码如下:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/>
<link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
<title>Thymeleaf</title>
</head>
<body>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">访问model</h3>
</div>
<div class="panel-body">
<span th:text="${singlePerson.name}"></span>
</div>
</div>
<div th:if="${not #lists.isEmpty(people)}">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">列表</h3>
</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item" th:each="person:${people}">
<span th:text="${person.name}"></span>
<span th:text="${person.age}"></span>
<button class="btn" th:οnclick="'getName(\''+${person.name}+'\');'">获得名字</button>
</li>
</ul>
</div>
</div>
</div>
<script th:src="@{jquery-3.2.1.min.js}" type="text/javascript"/>
<script th:src="@{bootstrap/bootstrap.min.js}"></script>
<script th:inline="javascript">
var single = [[${singlePerson}]];
console.log(single.name+"/"+single.age);
function getName(name) {
console.log(name);
}
</script>
</body>
</html>
5,编写一个controller,准备数据
package com.jack.web.controller;
import com.jack.web.pojo.Person;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.ArrayList;
import java.util.List;
@Controller
public class ThymeleafController {
@RequestMapping("/thymeleaf")
public String index(Model model){
Person single = new Person("jack", 11);
List<Person> people = new ArrayList<Person>();
Person p1 = new Person("xx", 12);
Person p2 = new Person("yy", 13);
Person p3 = new Person("zz", 14);
people.add(p1);
people.add(p2);
people.add(p3);
model.addAttribute("singlePerson",single);
model.addAttribute("people",people);
return "index";
}
}
6,修改application.yml
server:
port: 9090
7,运行
在浏览器输入http://localhost:9090/thymeleaf,效果如图所示:
单击“获得名字”选项,效果如图所示: