( 二 )、 SpringBoot 整合 Thymeleaf

( 二 )、 SpringBoot 整合 Thymeleaf

 

 

1、简介

官方文档: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

    Thymeleaf  是新一代 Java 模板引擎,它类似于 Velocity、FreeMarker 等传统 Java 模板引擎,但是与传统 Java 模板引擎不同的是,Thymeleaf 支持 HTML 原型。
它既可以让前端工程师在浏览器中直接打开查看样式,也可以让后端工程师结合真实数据查看显示效果,同时,SpringBoot 提供了 Thymeleaf 自动化配置解决方案,因此在 SpringBoot 中使用 Thymeleaf 非常方便。
事实上, Thymeleaf 除了展示基本的 HTML ,进行页面渲染之外,也可以作为一个 HTML 片段进行渲染,例如我们在做邮件发送时,可以使用 Thymeleaf 作为邮件发送模板。
另外,由于 Thymeleaf 模板后缀为 .html,可以直接被浏览器打开,因此,预览时非常方便。

 

2、整合

    <!--thymeleaf 模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

 

3、SpringBoot 针对 Thymeleaf 自动化配置 分析

1、Spring Boot 为 Thymeleaf 提供的自动化配置类,则是 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration ,部分源码如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
    
}

可以看到,在这个自动化配置类中,首先导入 ThymeleafProperties ,然后 @ConditionalOnClass 注解表示当当前系统中存在 TemplateMode 和 SpringTemplateEngine 类时,当前的自动化配置类才会生效,即只要项目中引入了 Thymeleaf 相关的依赖,这个配置就会生效。

这些默认的配置我们几乎不需要做任何更改就可以直接使用了。如果开发者有特殊需求,则可以在 application.properties 中配置以 spring.thymeleaf 开头的属性即可。

2、配置类的属性在 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties 中,部分源码如下:

@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";

    /**
     * Whether to check that the template exists before rendering it.
     */
    private boolean checkTemplate = true;

    /**
     * Whether to check that the templates location exists.
     */
    private boolean checkTemplateLocation = true;

    /**
     * Prefix that gets prepended to view names when building a URL.
     */
    private String prefix = DEFAULT_PREFIX;

    /**
     * Suffix that gets appended to view names when building a URL.
     */
    private String suffix = DEFAULT_SUFFIX;

    /**
     * Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
     */
    private String mode = "HTML";

    /**
     * Template files encoding.
     */
    private Charset encoding = DEFAULT_ENCODING;

    /**
     * Whether to enable template caching.
     */
    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;
}
  1. 首先通过 @ConfigurationProperties 注解,将 application.properties 前缀为 spring.thymeleaf 的配置和这个类中的属性绑定。
  2. 前三个 static 变量定义了默认的编码格式、视图解析器的前缀、后缀等。
  3. 从前三行配置中,可以看出来,Thymeleaf 模板的默认位置在 resources/templates 目录下,默认的后缀是 html 。
  4. 这些配置,如果开发者不自己提供,则使用 默认的,如果自己提供,则在 application.properties 中以 spring.thymeleaf 开始相关的配置。

 

4、案例

 1、创建 Controller

@Controller
public class ThymeleafTestController {

    /**
     * 使用 Model 传递属性, 并返回视图名: index
     * @param model
     * @return
     */
    @GetMapping("/index")
    public String index(Model model) {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User u = new User();
            u.setId((long) i);
            u.setName("dwTest:" + i);
            u.setAddress("成都:" + i);
            users.add(u);
        }
        model.addAttribute("users", users);
        return "index";
    }

    /**
     *  使用 Map 传递属性, 并返回视图:hello
     * @param map
     * @return
     */
    @RequestMapping("/hello")
    public String hello(Map<String, Object> map) {
        //通过 map 向前台页面传递数据
        map.put("name", "dwTest");
        return "hello";
    }

    public class User {
        private Long id;
        private String name;
        private String address;
        //省略 getter/setter
    }

}

 

2、创建 Thymeleaf

在 IndexController 中返回逻辑视图名+数据,逻辑视图名为 index ,意思我们需要在 resources/templates 目录下提供一个名为 index.html 的 Thymeleaf 模板文件。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1">
    <tr>
        <td>编号</td>
        <td>用户名</td>
        <td>地址</td>
    </tr>
    <tr th:each="user : ${users}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.name}"></td>
        <td th:text="${user.address}"></td>
    </tr>
</table>
</body>
</html>

在 Thymeleaf 中,通过 th:each 指令来遍历一个集合,数据的展示通过 th:text 指令来实现。

 

5、手动渲染

我们也可以手动渲染 Thymeleaf 模板,这个一般在邮件发送时候有用,例如我在 resources/templates 目录下新建一个邮件模板,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>hello 欢迎 <span th:text="${username}"></span>加入 XXX 集团,您的入职信息如下:</p>
<table border="1">
    <tr>
        <td>职位</td>
        <td th:text="${position}"></td>
    </tr>
    <tr>
        <td>薪水</td>
        <td th:text="${salary}"></td>
    </tr>
</table>
</body>
</html>

这一个 HTML 模板中,有几个变量,我们要将这个 HTML 模板渲染成一个 String 字符串,再把这个字符串通过邮件发送出去,那么如何手动渲染呢?

@Autowired
TemplateEngine templateEngine;
@Test
public void test1() throws MessagingException {
    Context context = new Context();
    context.setVariable("username", "javaboy");
    context.setVariable("position", "Java工程师");
    context.setVariable("salary", 99999);
    String mail = templateEngine.process("mail", context);
    //省略邮件发送
}
  1. 渲染时,我们需要首先注入一个 TemplateEngine 对象,这个对象就是在 Thymeleaf 的自动化配置类中配置的(即当我们引入 Thymeleaf 的依赖之后,这个实例就有了)。
  2. 然后构造一个 Context 对象用来存放变量。
  3. 调用 process 方法进行渲染,该方法的返回值就是渲染后的 HTML 字符串,然后我们将这个字符串发送出去。

 

6、Thymeleaf 语法规则

在使用 Thymeleaf 之前,首先要在页面的 html 标签中声明名称空间,示例代码如下:

  1. xmlns:th="http://www.thymeleaf.org"

在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。

Thymeleaf 作为一种模板引擎,它拥有自己的语法规则。Thymeleaf 语法分为以下 2 类:

  • 标准表达式语法
  • th 属性

2.1 标准表达式语法

Thymeleaf 模板引擎支持多种表达式:

  • 变量表达式:${...}
  • 选择变量表达式:*{...}
  • 链接表达式:@{...}
  • 国际化表达式:#{...}
  • 片段引用表达式:~{...}

2.1.1 变量表达式

使用 ${} 包裹的表达式被称为变量表达式,该表达式具有以下功能:

  • 获取对象的属性和方法
  • 使用内置的基本对象
  • 使用内置的工具对象


① 获取对象的属性和方法

使用变量表达式可以获取对象的属性和方法,例如,获取 person 对象的 lastName 属性,表达式形式如下:

  1. ${person.lastName}

② 使用内置的基本对象

使用变量表达式还可以使用内置基本对象,获取内置对象的属性,调用内置对象的方法。 Thymeleaf 中常用的内置基本对象如下:

  • #ctx :上下文对象;
  • #vars :上下文变量;
  • #locale:上下文的语言环境;
  • #request:HttpServletRequest 对象(仅在 Web 应用中可用);
  • #response:HttpServletResponse 对象(仅在 Web 应用中可用);
  • #session:HttpSession 对象(仅在 Web 应用中可用);
  • #servletContext:ServletContext 对象(仅在 Web 应用中可用)。

例如,我们通过以下 2 种形式,都可以获取到 session 对象中的 map 属性:

  1. ${#session.getAttribute('map')}
  2. ${session.map}

③ 使用内置的工具对象

除了能使用内置的基本对象外,变量表达式还可以使用一些内置的工具对象。

  • strings:字符串工具对象,常用方法有:equals、equalsIgnoreCase、length、trim、toUpperCase、toLowerCase、indexOf、substring、replace、startsWith、endsWith,contains 和 containsIgnoreCase 等;
  • numbers:数字工具对象,常用的方法有:formatDecimal 等;
  • bools:布尔工具对象,常用的方法有:isTrue 和 isFalse 等;
  • arrays:数组工具对象,常用的方法有:toArray、length、isEmpty、contains 和 containsAll 等;
  • lists/sets:List/Set 集合工具对象,常用的方法有:toList、size、isEmpty、contains、containsAll 和 sort 等;
  • maps:Map 集合工具对象,常用的方法有:size、isEmpty、containsKey 和 containsValue 等;
  • dates:日期工具对象,常用的方法有:format、year、month、hour 和 createNow 等。

例如,我们可以使用内置工具对象 strings 的 equals 方法,来判断字符串与对象的某个属性是否相等,代码如下。

  1. ${#strings.equals('dwTest', name)}

2.1.2 选择变量表达式

选择变量表达式与变量表达式功能基本一致,只是在变量表达式的基础上增加了与 th:object 的配合使用。当使用 th:object 存储一个对象后,我们可以在其后代中使用选择变量表达式(*{...})获取该对象中的属性,其中,“*”即代表该对象。

  1. <div th:object="${session.user}" >
  2. <p th:text="*{fisrtName}">firstname</p>
  3. </div>

th:object 用于存储一个临时变量,该变量只在该标签及其后代中有效,在后面的内容“th 属性”中我详细介绍。

2.1.3 链接表达式

不管是静态资源的引用,还是 form 表单的请求,凡是链接都可以用链接表达式 (@{...})。 链接表达式的形式结构如下: 无参请求:@{/xxx} 有参请求:@{/xxx(k1=v1,k2=v2)} 例如使用链接表达式引入 css 样式表,代码如下。

  1. <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">

2.1.4 国际化表达式

消息表达式一般用于国际化的场景。结构如下。

  1. th:text="#{msg}"

注意:此处了解即可,我们会在后面的章节中详细介绍。

2.1.5 片段引用表达式

片段引用表达式用于在模板页面中引用其他的模板片段,该表达式支持以下 2  中语法结构:

  • 推荐:~{templatename::fragmentname}
  • 支持:~{templatename::#id}


以上语法结构说明如下:

  • templatename:模版名,Thymeleaf 会根据模版名解析完整路径:/resources/templates/templatename.html,要注意文件的路径。
  • fragmentname:片段名,Thymeleaf 通过 th:fragment 声明定义代码块,即:th:fragment="fragmentname"
  • id:HTML 的 id 选择器,使用时要在前面加上 # 号,不支持 class 选择器。

2.2 th 属性

Thymeleaf 还提供了大量的 th 属性,这些属性可以直接在 HTML 标签中使用,其中常用 th 属性及其示例如下表:

属性描述示例
th:id 替换 HTML 的 id 属性
  • <input id="html-id" th:id="thymeleaf-id" />
th:text 文本替换,转义特殊字符
  • <h1 th:text="hello,bianchengbang" >hello</h1>
th:utext 文本替换,不转义特殊字符
  • <div th:utext="<h1>欢迎来到编程帮!</h1>" >欢迎你</div>
th:object 在父标签选择对象,子标签使用 *{…} 选择表达式选取值。
没有选择对象,那子标签使用选择表达式和 ${…} 变量表达式是一样的效果。
同时即使选择了对象,子标签仍然可以使用变量表达式。
  • <div th:object="${session.user}" >
  • <p th:text="*{fisrtName}">firstname</p>
  • </div>
th:value 替换 value 属性
  • <input th:value = "${user.name}" />
th:with 局部变量赋值运算
  • <div th:with="isEvens = ${prodStat.count}%2 == 0" th:text="${isEvens}"></div>
th:style 设置样式
  • <div th:style="'color:#F00; font-weight:bold'">编程帮 www.biancheng.net</div>
th:onclick 点击事件
  • <td th:onclick = "'getInfo()'"></td>
th:each 遍历,支持 Iterable、Map、数组等。
 
  • <table>
  • <tr th:each="m:${session.map}">
  • <td th:text="${m.getKey()}"></td>
  • <td th:text="${m.getValue()}"></td>
  • </tr>
  • </table>
th:if 根据条件判断是否需要展示此标签
  • <a th:if ="${userId == collect.userId}">
th:unless 和 th:if 判断相反,满足条件时不显示
  • <div th:unless="${m.getKey()=='name'}" ></div>
th:switch 与 Java 的 switch case语句类似
通常与 th:case 配合使用,根据不同的条件展示不同的内容
  • <div th:switch="${name}">
  • <span th:case="a">编程帮</span>
  • <span th:case="b">www.biancheng.net</span>
  • </div>
th:fragment 模板布局,类似 JSP 的 tag,用来定义一段被引用或包含的模板片段
  • <footer th:fragment="footer">插入的内容</footer>
th:insert 布局标签;
将使用 th:fragment 属性指定的模板片段(包含标签)插入到当前标签中。
  • <div th:insert="commons/bar::footer"></div>
th:replace 布局标签;
使用 th:fragment 属性指定的模板片段(包含标签)替换当前整个标签。
  • <div th:replace="commons/bar::footer"></div>
th:selected select 选择框选中
  • <select>
  • <option>---</option>
  • <option th:selected="${name=='a'}">
  • 编程帮
  • </option>
  • <option th:selected="${name=='b'}">
  • www.biancheng.net
  • </option>
  • </select>
th:src 替换 HTML 中的 src 属性 
  • <img th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" />
th:inline 内联属性;
该属性有 text,none,javascript 三种取值,
在 <script> 标签中使用时,js 代码中可以获取到后台传递页面的对象。
  • <script type="text/javascript" th:inline="javascript">
  • var name = /*[[${name}]]*/ 'bianchengbang';
  • alert(name)
  • </script>
th:action 替换表单提交地址
  • <form th:action="@{/user/login}" th:method="post"></form>

3. Thymeleaf 公共页面抽取

在 Web 项目中,通常会存在一些公共页面片段(重复代码),例如头部导航栏、侧边菜单栏和公共的 js css 等。我们一般会把这些公共页面片段抽取出来,存放在一个独立的页面中,然后再由其他页面根据需要进行引用,这样可以消除代码重复,使页面更加简洁。

3.1 抽取公共页面

Thymeleaf 作为一种优雅且高度可维护的模板引擎,同样支持公共页面的抽取和引用。我们可以将公共页面片段抽取出来,存放到一个独立的页面中,并使用 Thymeleaf 提供的 th:fragment 属性为这些抽取出来的公共页面片段命名。

示例 1

将公共页面片段抽取出来,存放在 commons.html 中,代码如下:

<div th:fragment="fragment-name" id="fragment-id">
<span>公共页面片段</span>
</div>

3.2 引用公共页面

在 Thymeleaf 中,我们可以使用以下 3 个属性,将公共页面片段引入到当前页面中。

  • th:insert:将代码块片段整个插入到使用了 th:insert 属性的 HTML 标签中;
  • th:replace:将代码块片段整个替换使用了 th:replace 属性的 HTML 标签中;
  • th:include:将代码块片段包含的内容插入到使用了 th:include 属性的 HTML 标签中。


使用上 3 个属性引入页面片段,都可以通过以下 2 种方式实现。

  • ~{templatename::selector}:模板名::选择器
  • ~{templatename::fragmentname}:模板名::片段名

通常情况下,~{} 可以省略,其行内写法为 [[~{...}]] 或 [(~{...})],其中  [[~{...}]] 会转义特殊字符,[(~{...})] 则不会转义特殊字符。

示例 2

1. 在页面 fragment.html 中引入 commons.html 中声明的页面片段,可以通过以下方式实现。

  1. <!--th:insert 片段名引入-->
  2. <div th:insert="commons::fragment-name"></div>
  3. <!--th:insert id 选择器引入-->
  4. <div th:insert="commons::#fragment-id"></div>
  5. ------------------------------------------------
  6. <!--th:replace 片段名引入-->
  7. <div th:replace="commons::fragment-name"></div>
  8. <!--th:replace id 选择器引入-->
  9. <div th:replace="commons::#fragment-id"></div>
  10. ------------------------------------------------
  11. <!--th:include 片段名引入-->
  12. <div th:include="commons::fragment-name"></div>
  13. <!--th:include id 选择器引入-->
  14. <div th:include="commons::#fragment-id"></div>


2. 启动 Spring Boot,使用浏览器访问 fragment.html,查看源码,结果如下。

  1. <!--th:insert 片段名引入-->
  2. <div>
  3. <div id="fragment-id">
  4. <span>公共页面片段</span>
  5. </div>
  6. </div>
  7. <!--th:insert id 选择器引入-->
  8. <div>
  9. <div id="fragment-id">
  10. <span>公共页面片段</span>
  11. </div>
  12. </div>
  13. ------------------------------------------------
  14. <!--th:replace 片段名引入-->
  15. <div id="fragment-id">
  16. <span>公共页面片段</span>
  17. </div>
  18. <!--th:replace id 选择器引入-->
  19. <div id="fragment-id">
  20. <span>公共页面片段</span>
  21. </div>
  22. ------------------------------------------------
  23. <!--th:include 片段名引入-->
  24. <div>
  25. <span>公共页面片段</span>
  26. </div>
  27. <!--th:include id 选择器引入-->
  28. <div>
  29. <span>公共页面片段</span>
  30. </div>

3.3 传递参数

Thymeleaf 在抽取和引入公共页面片段时,还可以进行参数传递,大致步骤如下:

  1. 传入参数;
  2. 使用参数。

3.3.1 传入参数

引用公共页面片段时,我们可以通过以下 2 种方式,将参数传入到被引用的页面片段中:

  • 模板名::选择器名或片段名(参数1=参数值1,参数2=参数值2)
  • 模板名::选择器名或片段名(参数值1,参数值2)

注:

  • 若传入参数较少时,一般采用第二种方式,直接将参数值传入页面片段中;
  • 若参数较多时,建议使用第一种方式,明确指定参数名和参数值,。

示例代码如下:

  1. <!--th:insert 片段名引入-->
  2. <div th:insert="commons::fragment-name(var1='insert-name',var2='insert-name2')"></div>
  3. <!--th:insert id 选择器引入-->
  4. <div th:insert="commons::#fragment-id(var1='insert-id',var2='insert-id2')"></div>
  5. ------------------------------------------------
  6. <!--th:replace 片段名引入-->
  7. <div th:replace="commons::fragment-name(var1='replace-name',var2='replace-name2')"></div>
  8. <!--th:replace id 选择器引入-->
  9. <div th:replace="commons::#fragment-id(var1='replace-id',var2='replace-id2')"></div>
  10. ------------------------------------------------
  11. <!--th:include 片段名引入-->
  12. <div th:include="commons::fragment-name(var1='include-name',var2='include-name2')"></div>
  13. <!--th:include id 选择器引入-->
  14. <div th:include="commons::#fragment-id(var1='include-id',var2='include-id2')"></div>

3.3.2 使用参数

在声明页面片段时,我们可以在片段中声明并使用这些参数,例如:

  1. <!--使用 var1 和 var2 声明传入的参数,并在该片段中直接使用这些参数 -->
  2. <div th:fragment="fragment-name(var1,var2)" id="fragment-id">
  3. <p th:text="'参数1:'+${var1} + '-------------------参数2:' + ${var2}">...</p>
  4. </div>

 

posted @ 2018-09-07 16:16  邓维-java  阅读(1009)  评论(0编辑  收藏  举报