项目源码寄存在Github repo

继上一篇 《Step by Step Tutorial for Spring Boot and Thymeleaf: Part I》 介绍了项目的初始环境,还有对应的Model,以及对应的Repository之后,这一篇开始涉及具体的Controller和View————这些还是基于MVC概念。

官方文档

官方Thymeleaf 与Spring Boot的文档:Thymeleaf Doc

列表页面

列表页面所有的Person Role。

Controller

这里的Controller其实支持了多个View。这里的两个method:

  • index: 主页面 (对应于URL: localhost:8080) ;
  • getPersonRoles: 显示Person Role列表 (对应于URL: localhost:8080/personroles);
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.alvachien.springtutorial.thymeleafjpademo.exception.ResourceNotFoundException;
import com.alvachien.springtutorial.thymeleafjpademo.model.PersonRole;
import com.alvachien.springtutorial.thymeleafjpademo.service.PersonRoleService;

@Controller
public class PersonRoleController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    private final int ROW_PER_PAGE = 5;
 
    @Autowired
    private PersonRoleService personRoleService;
 
    @Value("${msg.title}")
    private String title;
 
    @GetMapping(value = {"/", "/index"})
    public String index(Model model) { 
        model.addAttribute("title", title);
        return "index";
    }
 
    @GetMapping(value = "/personroles")
    public String getPersonRoles(Model model, @RequestParam(value = "page", defaultValue = "1") int pageNumber) 
    { 
        List<PersonRole> roles = personRoleService.findAll(pageNumber, ROW_PER_PAGE);
 
        long count = personRoleService.count();
        boolean hasPrev = pageNumber > 1;
        boolean hasNext = (pageNumber * ROW_PER_PAGE) < count;

        model.addAttribute("roles", roles);
        model.addAttribute("hasPrev", hasPrev);
        model.addAttribute("prev", pageNumber - 1);
        model.addAttribute("hasNext", hasNext);
        model.addAttribute("next", pageNumber + 1);
        return "person-role-list";    
    }
}

View

Thymeleaf的语法大致规律如下:

  • 需要在HTML的tag下设置对应XML的namespace:xmlns:th="http://www.thymeleaf.org"
  • 设置HTML中Tag的th属性:
    • th:text:设置当前元素的文本内容;
    • th:utext:设置文本内容。与th:text的区别在于,th:text不会转义html标签,而th:utext会。
    • th:value:设置当前元素的value值,类似修改指定属性的还有th:srcth:href
    • th:href:设置超链接。
    • th:each:遍历循环元素,通常和th:textth:value一起使用。
    • th:if:条件判断,类似的还有th:unlessth:switchth:case
    • th:insert:代码块。类似的还有th:replaceth:include,三者的区别较大,若使用不恰当会破坏html结构,常用于公共代码块提取的场景。
    • th:fragment:定义代码块,方便被th:insert引用。
    • th:object:声明变量,一般和*{}一起配合使用。
    • th:attr:修改任意属性,类似的还有th:attrappendth:attrprepend
  • 表达式语法
    • ${...}:变量表达式,通常跟th:text联动;
        <span th:text="${role.name}"> 
      
    • *{...}:选择表达式,它类似于变量表达式,但是它读取一个预先选择的对象,通常跟th:objectth:text联用。 ```html
      - `#{...}`: 消息/文字表达式,允许读取文字信息。
      ```html
      <div th:text="#[message.name]"></div>
    
    • @{...}:链接url表达式,把一个有用的上下文或回话信息添加到URL;
        <a th:href="@{/main}">Back to index</a>
      
    • ~{...}:代码块表达式;

主页(index.html)

对应的模板文件如下:

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title th:utext="${title}" />
    </head>
    <body>
        <h1 th:utext="${title}" />
        <a th:href="@{/personroles}">Person Roles List</a>  
    </body>
</html>

List页面 (person-role-list.html)

对应的模板文件如下:

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <title>Person Role List</title>
    </head>
    <body>
        <h1>Person Role List</h1>
        
        <div>
            <nobr>
                <a th:href="@{/}">Back to Index</a>
            </nobr>
        </div>
        <br/><br/>
        <div>
            <table border="1">
                <tr>
                    <th>Id</th>
                    <th>Name</th>
                    <th>Value</th>
                    <th></th>
                </tr>
                <tr th:each ="role : ${roles}">
                    <td><a th:href="@{/personroles/{roleId}(roleId=${role.getId()})}" th:utext="${role.getId()}">...</a></td>
                    <td><a th:href="@{/personroles/{roleId}(roleId=${role.getId()})}" th:utext="${role.getRoleName()}">...</a></td>
                    <td th:utext="${role.getRoleValue()}">...</td>
                    <td><a th:href="@{/personroles/{roleId}/edit(roleId=${role.getId()})}">Edit</a></td>
                </tr>
            </table>          
        </div>
        <br/><br/>
        <div>
            <nobr>
                <span th:if="${hasPrev}"><a th:href="@{/personroles?page={prev}(prev=${prev})}">Prev</a>&nbsp;&nbsp;&nbsp;</span> 
                <span th:if="${hasNext}"><a th:href="@{/personroles?page={next}(next=${next})}">Next</a></span>
            </nobr>
        </div>
    </body>
</html>

运行程序:

主页

列表页面