Thymeleaf的基础使用

114 阅读6分钟

写在前面:除摘要中的一些内容还有一些在使用Thymeleaf时可能遇到的小问题,由于写在一切可能篇幅过长内容过杂,因此这些小问题以及其部分解决方案放在下一篇文章中。最后,对于一些大牛来说这篇文章可能灌水严重,但本人是出于记录以及回忆之前知识的目的写下的此片文章,排版问题,内容有误请各位尽情指正

开门见山,先比较一下有/没有使用Thymeleaf的区别,好直观感受Thymeleaf能带给我们什么

没有添加Thymeleaf依赖的情况

在Spring Boot应用中,一般把网页存放在`src/main/resources/stati目录下 :

image.png

在Controller中,会自动加载static下的html内容,因此可以通过如下方式在网页中返回一个html页面:

 import org.springframework.stereotype.Controller;
 ​
 @Controller
 public class HelloControl {
 ​
     public String say(){
         return "hello.html";
     }
 ​
 }

定义返回类型为String
return "hello.html"返回的是html文件路径

当执行这段代码的时候,Spring Boot实际加载的是src/main/resources/static/hello.html 文件

return语句返回的是static目录下的文件,现在如果我们的html文件放在

image.png

则我们的代码应该是:

 import org.springframework.stereotype.Controller;
 ​
 @Controller
 public class HelloControl {
 ​
     public String say(){
         return "html/hello.html";
     }
 ​
 }

注意文件路径使用的是/进行分割

添加Thymeleaf依赖的情况

 @Controller
 public class SongListControl {
 ​
   @Autowired
   private SongListService songListService;
 ​
   @RequestMapping("/songlist")
   public String index(@RequestParam("id")String id,Model model){
 ​
     SongList songList = songListService.get(id);
     //传递歌单对象到模板当中
     //第一个 songList 是模板中使用的变量名
     // 第二个 songList 是当前的对象实例
     model.addAttribute("songList",songList);
 ​
     return "songList";
   }
 }

Spring MVC 中对于模板文件是有固定的位置存放位置,放在工程src/main/resources/templates 因此对于添加过Thymeleaf的工程:return "songList"其实会去查找src/main/resources/templates/songList.html文件
系统会自动匹配后缀,因此你不需要写成return "songList.html";

image.png

(仅为参考图片,图中文件名可能与上述描述不一致)

  • 模板文件的后缀虽然也是 .html,大部分内容和html很像,但因为它放置在`src/main/resources/templates 目录下,而且里面可以写变量th:text="${...}" ,所以它其实不是 HTML 文件,而是 thymeleaf 模板

    放在src/main/resources/static目录下的就不是模板,是静态文件

    ➤ 另外需要注意:xmlns:th="http://www.thymeleaf.org"写在html文件中的作用是能让软件识别thymeleaf语法

Thymeleaf用法

模板变量

读取变量:${variable symbol name}(固定格式)

一般情况下,模板的变量都是会存放在模板上下文中,所以我们如果想要调用变量,就需要先设置变量到模板上下文中去

即在方法中声明model.addAttribute("songList",songList);就可以完成 上下文变量的设置

  • 第一个参数设置的就是上下文变量名(变量名是可以随便定义)
  • 第二参数设置的是变量值(可以是任意的对象)

th是Thymeleaf 的缩写,所以你如果看到 th:开头的标签属性那么就代表使用的是Thymeleaf语法


  • 变量

    • 读取变量:${variable symbol name}(固定格式)

    一般情况下,如果想要调用变量,就需要先添加变量到模板上下文中去,即在方法中声明model.addAttribute("songList",songList);就可以完成

    上下文变量的设置

    第一个参数设置的就是上下文变量名(变量名是可以随便定义)
    第二参数设置的是变量值(可以是任意的对象)

    • th:text:动态替换掉html标签的内部文本内容

     <span th:text="${msg}">Hello</span> 这段代码的执行结果就是用变量msg的值替换了span标签内的Hello文本,若msg变量值为你好,则渲染后效果为:

<span>你好</span>

如果变量名值为一个对象,其中属性值是可以用"."一直点出来的,前提是这个对象得是POJO类

循环语句(可嵌套循环)

  • th:each

     <ul th:each="song : ${songs}">
     <li th:text="${song.name}">歌曲名称</li>
     </ul>
    

    ${songs} 是从模板上下文中获取 songs 这个变量

    song 是 ${songs} 变量遍历后的每一个对象

    ${song.name} 就可以读取遍历中歌曲名称了

          @RequestMapping("/demo")
           public String index(Model model){
             List<Song> songs = new ArrayList<>();
          
             Song song = new Song();
             song.setId("0001");
             song.setName("朋友");
             songs.add(song);
          
             song = new Song();
             song.setId("0002");
             song.setName("夜空中最亮的星");
             songs.add(song);
          
             model.addAttribute("songs",songs);
             return "demo";
           }
    
    
          <ul th:each="song,it: ${songs}">
           <li>
             <span th:text="${it.count}"></span>
             <span th:text="${song.name}"></span>
           </li>
          </ul>
    

    打印列表索引值:

image.png

th:each="song,it: ${songs}",你会发现多了一个,it 这个it是作为可选参数出行,如果定义了就可以通过这个it对象来获取更多关于统计的需求;

此处it.xxxit为官方变量名,不可更改为其他变量(例如:song.index……)

  • it.index

    当前迭代对象的index(从0开始计算),如果想从0开始显示行数用这个就可以了

  • it.count

    当前迭代对象的index(从1开始计算),如果显示行数用这个就可以了

  • it.size

    被迭代对象的大小,如果想获取列表长度,用这个就可以了

  • it.current

    当前迭代变量,等同与上面的song

  • it.even/odd

    布尔值,当前循环是否是偶数/奇数(从0开始计算)

  • it.first/last

    布尔值,当前循环是否是第一
    最后一个模板中的布尔值需要结合条件语句来处理

表达式

Thymelaeaf表达式主要用于两种场景

  • 字符串处理
  • 数据转化

字符串拼接

我们一般会看到视频这样的视频显示格式:00:00/45:00——代表这个视频从00:00开始,总共45分钟,在Thymeleaf中需要利用+来完成字符串的拼接

   @RequestMapping("/demo")
   public String index(Model model){
 ​
     String totalTime = "45:00";
 ​
     model.addAttribute("totalTime",totalTime);
     return "demo";
   }
 <span th:text="'00:00/'+${totalTime}"></span>

需要注意的是'00:00/'

''的作用:将文本变为java字符串,此时两个字符串可以利用+来进行拼接 ,因此当我们不使用''00:00/包裹住时

 <span th:text="00:00/+${totalTime}"></span>

程序会发生报错:

 Wed Jan 08 14:40:15 CST 2020
 There was an unexpected error (type=Internal Server Error, status=500).
 Could not parse as expression: "00:00/+${totalTime}" (template: "demo" - line 7, col 9)

字符串拼接优化

我们还可以使用||包裹住字符串,此时可免去+进行字符串的拼接

 <span th:text="|00:00/${totalTime}|"></span>

数据转化

Thymeleaf 默认集成了大量的工具类可以方便的进行数据转化,一般我们使用最多的是 dates

如果你想处理 LocalDateLocalDateTime 类,你可以在pom.xml添加如下依赖

 <dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-java8time</artifactId>
   <version>3.0.4.RELEASE</version>
 </dependency>

这个库会自动添加一个新的工具类 temporals

工具类运用与变量的不同:变量使用 ${变量名} ,工具类使用 #{工具类}

  • dates/temporals

dates 和temporals 支持的方法是一样的,只是支持的类型不同,dates 支持的是Date类,temporals 支持的是 LocalDate和 LocalDateTime

java.util.Date 类和 LocalDateTime 类功能是一样的,不同的是 LocalDateTime是Java8才出现的,一些老的应用还是用Date

类的。具体可以搜索一下看看文档

   @RequestMapping("/demo")
   public String index(Model model){
 ​
     Date dateVar = new Date();
 ​
     model.addAttribute("dateVar",dateVar);
     return "demo";
   }

我们一般使用 dates/temporals 用于处理日期类型到字符串的转化,比如显示年月日

 <p th:text="${#dates.format(dateVar, 'yyyy-MM-dd')}"></p>
 <p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日')}"></p>

显示时分秒

 <p th:text="${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
 <p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>

假如日期类型是LocalDate/LocalDateTime那么就把#dates换成#temporals

Strings

  • ${#strings.toUpperCase(name)}

把字符串改成全大写

  • ${#strings.toLowerCase(name)}

把字符串改成全小写

  • ${#strings.arrayJoin(array,',')}

把字符串数组合并成一个字符串,并以,连接,比如["a","b"]执行后会变成a,b

  • ${#strings.arraySplit(str,',')}

把字符串分隔成一个数组,并以,作为分隔符,比如a,b执行后会变成 ["a","b"]

如果 abc 没有匹配到,执行后会变成["abc"]

  • ${#strings.trim(str)}

把字符串去空格,左右空格都会去掉

  • ${#strings.length(str)}

得到字符串长度,也支持获取集合类长度

  • ${#strings.equals(str1,str2)}

比较两个字符串是否相等

  • ${#strings.equalsIgnoreCase(str1,str2)}

忽略大小写比较两个字符串是否相等

内敛表达式

[[变量]]支持我们在HTML中调用变量

使用内敛表达式可以免去在标签属性内写th:text:#{xxxxx}

  @RequestMapping("/demo")
   public String index(Model model){
     String msg = "LEVI";
 ​
     model.addAttribute("msg",msg);
     return "demo";
   }
 <span>Hello [[${msg}]]</span>
 <span>[[|Hello ${msg}|]]</span>

利用内敛表达式执行新语法

 <p>[[ ${#dates.format(dateVar, 'yyyy-MM-dd')} ]]</p>
 <p>[[${#dates.format(dateVar, 'yyyy年MM月dd日')}]]</p>
 ​
 <p>[[${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}]]</p>
 <p>[[${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}]]</p>

th:text[[]]两种写法都是允许的,每个团队可能规则不一样。但要了解的是[[]]是用来替代`th:text 的,不是替代所有 th: 标签的

条件语句

有时我们会遇到显示性别的情况

  • user.sex值为male显示:男
  • user.sex值为female显示:女

Thymeleaf 也支持 if/else 能力,同样也是以 th:开头的属性,这次我们使用的是 th:if,if表达式的值是 ture 的情况下就会执行渲染

 <span th:if="${user.sex == 'male'}">男</span>

还可以使用th:unless代表否定条件,这个语句与if是相反的,代表的是sex不是male的情况下才进行渲染

 <span th:unless="${user.sex == 'male'}">女</span>

th:if条件判断除了判断boolean值外,Thymeleaf还认为如下表达式为true

  • 值非空
  • 值是非0数字
  • 值是字符串,但是不是falseoff or no
  • 值不是boolean值,数字,character或字符串

String 逻辑判断

有时我们还会借助#Strings这个内置对象来做逻辑判断和处理

  • 检查字符串是否为空(或null),在检查之前会先进行trim()
 ${#strings.isEmpty(name)}
  • 数组同样适用
 ${#strings.arrayIsEmpty(name)}
  • 集合类同样适用
 ${#strings.listIsEmpty(name)}
  • 检查字符串变量是否包含片段
 ${#strings.contains(name,'abc')}
  • ${#strings.containsIgnoreCase(name,'abc')}

忽略大小写字母判断是否包含字符串

  • ${#strings.startsWith(name,'abc')}

是否以字符串开头

  • ${#strings.endsWith(name,'abc')}

是否以字符串结束

Thymeleaf布局

推荐使用th:include + th:replace方案完成布局开发

layout.html:

 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <title>布局</title>
     <style>
         .header {background-color: #f5f5f5;padding: 20px;}
         .header a {padding: 0 20px;}
         .container {padding: 20px;margin:20px auto;}
         .footer {height: 40px;background-color: #f5f5f5;border-top: 1px solid #ddd;padding: 20px;}
     </style>
 </head>
 <body>
 <header class="header">
     <div>
         <a href="/book/list.html">图书管理</a>
         <a href="/user/list.html">用户管理</a>
     </div>
 </header>
 <div class="container" th:include="::content">页面正文内容</div>       
 <footer class="footer">
     <div>
         <p style="float: left">&copy; youkeda.com 2017</p>
         <p style="float: right">
             Powered by 优课达
         </p>
     </div>
 </footer>
 ​
 </body>

user/list.html:

 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="layout">
 <div th:fragment="content">
     <h2>用户列表</h2>
     <div>
         <a href="/user/add.html">添加用户</a>
     </div>
     <table>
         <thead>
         <tr>
             <th>
                 用户名称
             </th>
             <th>
                 用户年龄
             </th>
             <th>
                 用户邮箱
             </th>
         </tr>
         </thead>
         <tbody>
         <tr th:each="user : ${users}">
             <td th:text="${user.name}"></td>
             <td th:text="${user.age}"></td>
             <td th:text="${user.email}"></td>
         </tr>
         </tbody>
     </table>
 </div>
 ​
 </html>
  • th:include = "::content"(名称不必一定为content,可根据实际需求修改,只要和需引入的片段th:fragment = "xxxx"中的xxxx名字相同就好)

::content指的是选择器,这个选择器指的就是将th:fragment值为content的页面加载过来;

当页面渲染的时候,布局会合并content这个fragment内容一起渲染

  • th:replace = "layout"

指定了布局的名称,一旦声明以后,页面会被替换成layout内容,这个layout指的是html文件templates/layout.html;

  • th:fragment = "content"

当页面渲染的时候,可以通过选择器指定使用这个片段,在上面layout.html文件的th:include = "::content"指定的就是这个值

 <div th:fragment="content">
 </div>

th:replace/include的另一种用法

th:replace 语法,不仅可以指定布局,也可以指定页面内容片段。

通常,在页面(例如首页)的某个节点(不是最外层)上,使用 th:replace="player::player" 引入另一个内容片段。

首页 index.html 代码示例如下:

 <div class="player" th:replace="player::player"></div>

::player 指的是内容片段 player.html 模板文件。::player 指的是片段模板文件的 th:fragment 的值:

player.html 文件代码示例如下:

 <div th:fragment="player">
   <div class="txt"> 下载豆瓣FM APP<br> 让好音乐继续 </div>
 </div>

这样,就完成了在首页中引入 player.html 文件内容。

可以引入多个部分内容,将整个页面模块化,避免首页文件中的代码过于臃肿。

Any correciton will be appreciate!!!