Spring Boot 【2】 使用 Thymeleaf 模板引擎

现在网站大致就是两种方案,服务器端渲染HTML或者单页面应用+API的方式。

服务器端渲染以前有 JSP 这样的方案,但由于 JSP 中要夹杂很多的 Java 代码,和前端开发一起工作的时候会前端可能会看不懂 JSP 的页面,不知道哪些部分可以修改。
而使用 Thymeleaf 来进行开发的话,它的语法就和现在的前端框架很接近。而且 Thymeleaf 的 HTML 文件脱离了我们 Java 后端的数据,依然可以在浏览器中看到页面的效果,对前端开发比较友好。
当然,如果写习惯了 JSP,也没有使用过一些常见的前端框架的话,可能就对 Thymeleaf 的语法比较陌生了。  

开始使用

我们这个项目是 Maven 构建的,所以先在 pom.xml 中添加所需要的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

保存之后,IntelliJ IDEA 会提示我们是否导入我们添加的依赖,点击 Enable Auto-Import ,以后我们修改了 pom.xml 中的依赖它就会自动开始下载了。

打开 resources 下的 application.properties ,在里面添加一行 spring.thymeleaf.cache=false 这样子可以关闭掉 Thymeleaf 的缓存,target 中的文件改变之后不需要重启 Spring Boot 应用(Tomcat)就可以刷新看到变化。

打开之前创建的 HomeController ,然后我们把 index 方法改成这样

@GetMapping("/")
public String index(Model model) {
    model.addAttribute("message", "Hello Thymeleaf");
    return "index";
}

接下来我们要在 resources 目录的 templates 下创建一个 index.html

index.html 的内容如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title th:text="${message}"></title>
</head>
<body>
<h3 th:text="${message}">
</h3>
</body>
</html>

此时我们重新运行一下,打开 http://localhost:8080/

页面中显示出了Hello Thymeleaf

解释

回到 HomeController 中,我们给 index 方法添加了一个 Model 类的参数,通过这个参数,可以给 Thymeleaf 页面传值。

Spring MVC 里面有一个 ModelAndView 的类,用于放置渲染的页面和数据,其中里面用到了 ModelMap 这个类。

可以知道我们给 index 添加的这个 Model 接口, Spring 在运行的时候会使用 ModelMap 这个实现,是基于 LinkedHashMap 的。

model.addAttribute(String, Object)

查看实现代码可以知道,这个最终调用的是 Map 的 put 方法,所以当作是 Map 来用就可以了。

return "index";

然后 index 的返回值我们写的是 “index”,他会在 templates 下寻找 index.html 文件,在这里我们不需要添加 .html 的后缀名。

接下来我们看一下 index.html。

首先在 html 标签中添加了一个 xmlns:th=”http://www.thymeleaf.org" 的命名空间。
Thymeleaf 的标签都是 th 开头的,这里使用了 th:text 属性,th:text 会改变这个html标签中的值。

${message} 代表的是取出我们在 model 中添加的键为 message 的数据。 所以 <h3 th:text="${message}"></h3> 将会渲染出 <h3>Hello Thymeleaf</h3>

用 Thymeleaf 展示对象中的内容

我们可以创建一个 Person 类,他有 id、 name、 age 三个属性。

public class Person {
    private long id;
    private String name;
    private int age;
    public Person() {
    }
    public Person(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

为了使 Jackson 正常工作,我们需要为它添加无参数构造器,全参数构造器,以及所有的 Getter 和 Setter。 也可以选择使用 Lombok。

接下来在 HomeController 中再添加一个页面

@GetMapping("/person")
public String person(Model model) {
    Person person = new Person(1,"David", 25);
    model.addAttribute("person",person);
    return "person";
}

这里创建了一个 Person 对象,然后把它添加到 model 里面。

接下来我们创建一个 person.html,内容如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>Person</title>
</head>
<body>
<p>
    ID: <span th:text="${person.id}"></span><br>
    Name: <span th:text="${person.name}"></span><br>
    Age: <span th:text="${person.age}"></span><br>
</p>
</body>
</html>

看到代码应该就懂了,我们可以通过 person.<属性> 获取到 person 对象的各种值。

浏览器中运行的效果:
 

展示 List 中的内容

我们再添加一个页面

@GetMapping("/personList")
public String personList(Model model) {
    List<Person> personList = new ArrayList<>();
    personList.add(new Person(1,"David", 25));
    personList.add(new Person(2,"Kevin", 30));
    personList.add(new Person(3,"Jenny", 23));
    model.addAttribute("personList", personList);
    return "personList";
}

在 personList 中添加了三个 Person 对象,然后添加到 model 里。

personList.html 代码如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>Person List</title>
</head>
<body>
<p th:each="person : ${personList}">
    ID: <span th:text="${person.id}"></span><br>
    Name: <span th:text="${person.name}"></span><br>
    Age: <span th:text="${person.age}"></span><br>
</p>
</body>
</html>

这里使用了一个 th:each 的属性。其实使用方法就相当于 forEach 循环。

可以看到它把 List 中的内容都展示出来了。