Spring Boot实战(七)Spring Boot 的 Web 开发 7.2 Thymeleaf模板引擎
7.2 Thymeleaf基础知识
Thymeleaf是一个Java类库,它是一个 xml/xhtml/html5的模板引擎,可以作为MVC的Web应用的View层。
Thymeleaf还提供了额外的模块与Spring MVC集成,所以我们可以使用Thymeleaf完全替代JSP。
下面我们演示日常工作中常用的Thymeleaf用法,我们将把本节的内容在7.2.4节运行演示。
1.引入 Thymeleaf
下面的代码是一个基本的Thymeleaf模板页面,在这里我们引入了Bootstrap(作为样式控制)和jQuery(DOM)操作,当然它们不是必须的:
<!DOCTYPE html>
<html xmlns:th="http:www.thymeleaf.org"> <!-- 通过此命名空间,将镜头页面转换为动态的视图。需要进行动态处理的元素将使用“th:”为前缀 -->
<head>
<meta content="text/html;charset=UTF-8">
<link th:src="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet" /><!-- 通过@{}引用Web静态资源,这在JSP下是极易出错的 -->
<title>Insert title here</title>
</head>
<body>
<script th:src="@{jquery-1.10.2.min.js}" type="text/javascript"></script>
<script th:src="@{bootstrap/js/bootstrap.min.js}"></script>
</body>
</html>
2.访问model中的数据
通过“${}”访问model中的属性,这和JSP极为相似。
<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>
3.model中的数据迭代
Thymeleaf的迭代和JSP的写法也很相似,代码如下:
<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>
</li>
</ul>
</div>
</div>
4.数据判断
代码如下:
<div th:if="${not #list.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>
</li>
</ul>
</div>
</div>
</div>
5.在JavaScript中访问model
在项目中,我们经常需要在JavaScript访问model中的值,在Thymeleaf实现代码如下:
<script th:inline="javascript">
var single = [[${singlePerson}]];
console.log(single.name+"/"+single.age);
</script>
还有一种是需要在html的代码里访问model中的属性,例如,我们需要在列表后单击每一行后面的按钮获得model中的值,可做如下处理:
<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:onclick="'getName(\'' + ${person.name} + '\');'">获得名字</button>
</li>
6.其它知识
更多完整的Thymeleaf的知识,请查看http://www.thymeleaf.org的官网。
7.2.2 与Spring MVC集成
在Spring MVC中,若我们需要集成一个模板引擎的话,需要定义ViewResolver,而ViewResolver需要定义一个View,如4.2.2节中我们为JSP定义的ViewResolver的代码:
@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new
InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
通过上面的代码可以看出,使用JsltView定义一个InternalResourceViewResolver,因而使用Thymeleaf作为我们的模板引擎也应该做类似的定义。庆幸的是,Thymeleaf为我们定义好了org.thymeleaf.spring4.view.ThymeleafView 和 org.thymeleaf.spring4.view.ThymeleafViewResolver(默认使用ThymeleafView作为View)。Thymeleaf给我们提供了一个SpringTemplateEngine类,用来驱动在Spring MVC下使用Thymeleaf模板引擎,另外还提供了一个TemplateResolver用来设置通用的模板引擎(包含前缀、后缀等),这使我们在Spring MVC中集成Thymeleaf引擎变得十分简单。
7.2.3 Spring Boot的Thymeleaf支持 在上一节我们讲述了Thymeleaf与Spring MVC集成的配置,讲述的目的是为了方便大家理解Spring MVC 和Thymeleaf集成的原理。但在Spring Boot中这一切都是不需要的,Spring Boot通过 org.springframework.boot.autoconfigure.thymeleaf包对Thymeleaf进行了自动配置。如图
通过ThymeleafAutoConfiguration类对集成所需要的Bean进行自动配置,包括templateResolver、templateEngine和thymeleafViewResolver的配置。
通过ThymeleafProperties来配置Thymeleaf,在application.properties中,以spring.thymeleaf开头来配置,通过查看ThymeleafProperties的主要源码,我们可以看出如何设置属性以及默认配置:
/*
* Copyright 2012-2018 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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.MediaType;
import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
/**
* Properties for Thymeleaf.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @author Daniel Fern谩ndez
* @author Kazuki Shimizu
* @since 1.2.0
*/
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
/**
* 前缀设置,Spring Boot默认模板,放置在classpath:/template/目录下
*/
private String prefix = DEFAULT_PREFIX;
/**
* 后缀设置,默认为html
*/
private String suffix = DEFAULT_SUFFIX;
/**
* 模板模式设置,默认为 HTML
*/
private String mode = "HTML";
/**
* 模板编码设置,默认为UTF-8
*/
private Charset encoding = DEFAULT_ENCODING;
/**
* 是否开启模板缓存,默认开启,开发时请关闭
*/
private boolean cache = true;
/**
* Order of the template resolver in the chain. By default, the template resolver is
* first in the chain. Order start at 1 and should only be set if you have defined
* additional "TemplateResolver" beans.
*/
private Integer templateResolverOrder;
/**
* Comma-separated list of view names (patterns allowed) that can be resolved.
*/
private String[] viewNames;
/**
* Comma-separated list of view names (patterns allowed) that should be excluded from
* resolution.
*/
private String[] excludedViewNames;
/**
* Enable the SpringEL compiler in SpringEL expressions.
*/
private boolean enableSpringElCompiler;
/**
* Whether hidden form inputs acting as markers for checkboxes should be rendered
* before the checkbox element itself.
*/
private boolean renderHiddenMarkersBeforeCheckboxes = false;
/**
* Whether to 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;
}
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 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;
}
public boolean isEnableSpringElCompiler() {
return this.enableSpringElCompiler;
}
public void setEnableSpringElCompiler(boolean enableSpringElCompiler) {
this.enableSpringElCompiler = enableSpringElCompiler;
}
public boolean isRenderHiddenMarkersBeforeCheckboxes() {
return this.renderHiddenMarkersBeforeCheckboxes;
}
public void setRenderHiddenMarkersBeforeCheckboxes(
boolean renderHiddenMarkersBeforeCheckboxes) {
this.renderHiddenMarkersBeforeCheckboxes = renderHiddenMarkersBeforeCheckboxes;
}
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");
/**
* Whether Thymeleaf should start writing partial output as soon as possible or
* buffer until template processing is finished.
*/
private boolean producePartialOutputWhileProcessing = true;
public MimeType getContentType() {
return this.contentType;
}
public void setContentType(MimeType contentType) {
this.contentType = contentType;
}
public boolean isProducePartialOutputWhileProcessing() {
return this.producePartialOutputWhileProcessing;
}
public void setProducePartialOutputWhileProcessing(
boolean producePartialOutputWhileProcessing) {
this.producePartialOutputWhileProcessing = producePartialOutputWhileProcessing;
}
}
public static class Reactive {
/**
* Maximum size of data buffers used for writing to the response. Templates will
* execute in CHUNKED mode by default if this is set.
*/
private DataSize maxChunkSize = DataSize.ofBytes(0);
/**
* Media types supported by the view technology.
*/
private List<MediaType> mediaTypes;
/**
* Comma-separated list of view names (patterns allowed) that should be executed
* in FULL mode even if a max chunk size is set.
*/
private String[] fullModeViewNames;
/**
* Comma-separated list of view names (patterns allowed) that should be the only
* ones executed in CHUNKED mode when a max chunk size is set.
*/
private String[] chunkedModeViewNames;
public List<MediaType> getMediaTypes() {
return this.mediaTypes;
}
public void setMediaTypes(List<MediaType> mediaTypes) {
this.mediaTypes = mediaTypes;
}
public DataSize getMaxChunkSize() {
return this.maxChunkSize;
}
public void setMaxChunkSize(DataSize maxChunkSize) {
this.maxChunkSize = maxChunkSize;
}
public String[] getFullModeViewNames() {
return this.fullModeViewNames;
}
public void setFullModeViewNames(String[] fullModeViewNames) {
this.fullModeViewNames = fullModeViewNames;
}
public String[] getChunkedModeViewNames() {
return this.chunkedModeViewNames;
}
public void setChunkedModeViewNames(String[] chunkedModeViewNames) {
this.chunkedModeViewNames = chunkedModeViewNames;
}
}
}
7.2.4 实战
1.新建Spring Boot项目
选择Thymeleaf依赖,spring-boot-starter-thymeleaf 会自动包含spring-boot-starter-web,如图
2.示例JavaBean
此类用来在模板页面展示数据用,包含name属性和age属性:
package com.wisely;
public class Person {
private String name;
private Integer age;
public Person() {
super();
}
public Person(String name,Integer age) {
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:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<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>Insert title here</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:onclick="getName([[${person.name}]]);">获得名字</button>
</li>
</ul>
</div>
</div>
</div>
<script th:src="@{jquery-1.6.2.min.js}" type="text/javascript"></script>
<script th:src="@{bootstrap/js/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.数据准备
代码如下:
package com.wisely;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@SpringBootApplication
public class Ch72Application {
@RequestMapping("/")
public String index(Model model) {
Person single = new Person("aa", 11);
List<Person> people = new ArrayList<Person>();
Person p1 = new Person("xx", 11);
Person p2 = new Person("yy", 22);
Person p3 = new Person("zz", 33);
people.add(p1);
people.add(p2);
people.add(p3);
model.addAttribute("singlePerson",single);
model.addAttribute("people",people);
return "index";
}
public static void main(String[] args) {
SpringApplication.run(Ch72Application.class, args);
}
}
6.运行
访问http://localhost:8080,效果如图
单击“获得名字” 选项,效果如图