• 中文
    • English
  • 注册
  • 查看作者
  • SpringBoot:关于thymeleaf的简单使用

    一.  数据准备

    1.  想要使用thymeleaf,需要先在pom.xml中添加相关依赖

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

    2. 接下来准备再一个简单的页面index.html,在resources下的templates文件下,新建index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>我是孙著杰的同桌</h1>
    </body>
    </html>

    3.  最后编写Controller

    package io.zhangjia.springboot3.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @Controller
    public class WebController {
    
        @GetMapping("/index")
        public String index(){
            return "index";
        }
    }

    4.  接下来访问http://localhost:8888/index,即可显示:我是孙著杰的同桌,可能细心的同学已经注意到,我们并没有配置视图解析器为return “index”添加前缀和后缀,但是为什么依旧能够成功访问index.html呢?原因是因为SpringBoot已经默认为我们配置了视图解析器,打开External中的Maven:org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE,找到thymeleaf—>ThymeleafAutoConfiguration,点击其中ThymeleafProperties.class,即可看到以下内容:

    @ConfigurationProperties(
        prefix = "spring.thymeleaf"
    )
    public class ThymeleafProperties {
        private static final Charset DEFAULT_ENCODING;
        public static final String DEFAULT_PREFIX = "classpath:/templates/";
        public static final String DEFAULT_SUFFIX = ".html";
        ....
        private boolean cache;
        ....
    }

    所以我们只需要将html页面放在类路径的templates文件下,页面便可正常被转发。

    5.  接下来再次修改html页面, 添加以下内容:

    <h1>孙著杰是我的同桌</h1>

    右击选择Recompile ' index. html' (快捷键 Ctrl+Shift+F9,相当于之前的packge),此时回到http://localhost:8888/index,并刷新页面,新添加的h1标签的内容却没有显示,这是因为缓存默认被关闭,在配置文件中将其开启即可:

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
        username: root
        password: zhangjia
        type: com.alibaba.druid.pool.DruidDataSource
      thymeleaf:
        cache: false

    三.  th的简单使用

    修改Controller,添加某个数据到Request作用域

    @Controller
    public class WebController {
    
        @GetMapping("/index")
        public String index(Model model){
            String name = "zhangjia";
            model.addAttribute("name",name);
            return "index";
        }
    }

    因为此时的视图页面不再是.jsp,而是.html,所以我们可以使用thymeleaf来显示数据,使用thymeleaf可以实现静动分离,举个简单的例子,在之前的项目练习中,我们都是先从网上找到相应的前端模板,然后将其后缀从html修改为jsp,假如该模板中有一个h2标签用于显示用户姓名:

    <h2>姓名</h2>

    则我们需要先将姓名而在删除,将其修改为:

    <h2>${name}</h2>

    而如果使用thymeleaf,我们便可以不删除姓名,而是采用下面的方式:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h2 th:text="${name}">姓名</h2>
    </body>
    </html>

    此时访问http://localhost:8888/index,页面却不是姓名二字,而是显示zhangjia

    同理,如果是其他属性,也可以直接在th后面修改,比如修改input:

    <input th:type="password" placeholder="请输入密码" th:class="jia-input"/>

    但是值得注意的是,a标签的href,或者图片的src属性等在thymeleaf中不能通过上述方式直接使用th编写,而需要使用@{xxx}将地址包裹:

    <a href="#" th:href="@{https://www.google.com/}">谷歌</a>

    同理,css、js等文件的引入如果使用的是在线资源, 是不需要动态获取的,但是如果使用的是本地资源,则必须动态获取

    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css">
        <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
    <!--如果使用本地的css文件,则这样写:
    <link rel="stylesheet" href=".../static/css/bootstrap.min.css"> 只这样写,是不能动态获取的,只有静态获取才能成功
    需要改成下面的样式
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" href=".../static/css/bootstrap.min.css">
    -->
    </head>

    注意,动态获取的时候资源地址不需要添加../static/,原因如下:

    我们可以打开External中的Maven:org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE,找到web—>Servlet—>ResourceProperties,即可看到以下内容:

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

    也就是说,我们的静态资源的存放位置有上面的四种方式,所以在动态获取的时候,不需要再添加../static

    三.  循环迭代

    假如我们有以下内容的JSON字符串

    [{"address":{"create_time":1563861382000,"user_id":4001,"name":"张三","tel":"13666666666","addr":"山东省济宁市","is_default":1,"aid":6001,"status":1},"create_time":1563931245000,"total_price":19964.0,"user_id":4001,"order_id":7001,"status":1,"products":[{"quantity":2,"img_url":"59ec3325N906f107e.jpg","price":8588.0,"dbid":8001,"product_id":3010,"name":"Apple 苹果 iPhone X 全面屏手机 深空灰色 全网通 64GB","order_id":7001},{"quantity":1,"img_url":"5abb0fe7N5f2b7ed2.jpg","price":2788.0,"dbid":8002,"product_id":3022,"name":"Apple iPad 平板电脑 2018年新款9.7英寸(128G WLAN版/A10芯片/Retina屏 MR7J2CH/A)深空灰色","order_id":7001}]},{"address":{"create_time":1563861427000,"user_id":4001,"name":"李四","tel":"13888889999","addr":"山东省青岛市","is_default":0,"aid":6002,"status":1},"create_time":1563935813000,"total_price":2599.0,"user_id":4001,"order_id":7002,"status":1,"products":[{"quantity":1,"img_url":"597ae1efNc6dd8fe4.jpg","price":2599.0,"dbid":8003,"product_id":3012,"name":"小米6 全网通 6GB+128GB 陶瓷黑尊享版 移动联通电信4G手机 双卡双待","order_id":7002}]}]

    将该json字符串格式化后显示如下:

    [
        {
            "address":{
                "create_time":1563861382000,
                "user_id":4001,
                "name":"张三",
                "tel":"13666666666",
                "addr":"山东省济宁市",
                "is_default":1,
                "aid":6001,
                "status":1
            },
            "create_time":1563931245000,
            "total_price":19964,
            "user_id":4001,
            "order_id":7001,
            "status":1,
            "products":[
                {
                    "quantity":2,
                    "img_url":"59ec3325N906f107e.jpg",
                    "price":8588,
                    "dbid":8001,
                    "product_id":3010,
                    "name":"Apple 苹果 iPhone X 全面屏手机 深空灰色 全网通 64GB",
                    "order_id":7001
                },
                {
                    "quantity":1,
                    "img_url":"5abb0fe7N5f2b7ed2.jpg",
                    "price":2788,
                    "dbid":8002,
                    "product_id":3022,
                    "name":"Apple iPad 平板电脑 2018年新款9.7英寸(128G WLAN版/A10芯片/Retina屏 MR7J2CH/A)深空灰色",
                    "order_id":7001
                }
            ]
        },
        {
            "address":{
                "create_time":1563861427000,
                "user_id":4001,
                "name":"李四",
                "tel":"13888889999",
                "addr":"山东省青岛市",
                "is_default":0,
                "aid":6002,
                "status":1
            },
            "create_time":1563935813000,
            "total_price":2599,
            "user_id":4001,
            "order_id":7002,
            "status":1,
            "products":[
                {
                    "quantity":1,
                    "img_url":"597ae1efNc6dd8fe4.jpg",
                    "price":2599,
                    "dbid":8003,
                    "product_id":3012,
                    "name":"小米6 全网通 6GB+128GB 陶瓷黑尊享版 移动联通电信4G手机 双卡双待",
                    "order_id":7002
                }
            ]
        }
    ]

    可以看到该JSON字符串中存放了一个用户的两个订单信息,其中第一个订单中有两个商品,第二个订单中有一个商品,接下来在Controller中将字符串转换为Java对象,并将其存入request作用域,并在视图中显示相关的数据,如果是JSP页面,一般我们会采用JSTL和EL来遍历处理这类数据,而thymeleaf也提供了相应的循环遍历的语法,即:th:each:

     <div th:each="order : ${orders}" class="card">
                    <div class="card-header">
          <!--           方法一:  -->
    <!--   <span th:text="${'下单时间:' + order.create_time}">下单时间:2019-6-7 16:13:28</span>-->
         <!--           方法二:  -->
                       <span th:text="${'下单时间:' + #dates.format(order.create_time, 'yyyy-MM-dd sHH:mm:ss')}">下单时间:2019-6-7 16:13:28</span>
                        <span th:text="${'订单号:' + order.order_id}">订单号:14</span>
                    </div>
                    <div class="card-body">
                        <table class="table table-sm table-bordered">
                            <tr th:each="product,status : ${order.products}">
                                <td class="w-100px">
                                    <img class="img-thumbnail" width="70px" height="70px" src="http://iph.href.lu/80x80?text=1" alt="">
                                </td>
                                <td th:text="${product.name}">
                                    Apple iPhone XR (A2108) 128GB 黑色 移动联通电信4G手机 双卡双待
                                </td>
                                <td class="w-50px text-center" th:text="${product.quantity}">1</td>
                                <td class="w-100px text-center" th:text="${#numbers.formatCurrency(product.price)}">¥6699.00</td>
                                <td class="w-120px" th:if="${status.index == 0}" th:rowspan="${status.size}" rowspan="2" >
    
                                    <span th:text="${order.address.name}">张三<br></span>
    
                                    <span th:text="${order.address.tel}">186XXXX2865<br></span>
    
                                    <span th:text="${order.address.addr}">山东省济宁市高新区XX路XX大厦XXX室</span>
                                </td>
                                <td class="w-120px" th:if="${status.index == 0}" th:rowspan="${status.size}" rowspan="2">
                                    总金额:<br>
                                    <span th:text="${#numbers.formatCurrency(order.total_price)}">¥31,495.00</span>
                                </td>
                            </tr>
    
                        </table>
                    </div>
                </div>

    在上⾯代码中,th:each=”order : ${orders}” 的功能其实类似于foreach:即for(var order: orders) ,也就是说orders是被迭代的变量,而order为迭代变量,orders中存放了两个订单中的所有数据,而每个order中存放的都是一个订单的数据。

    同理,<tr th:each=”product,status : ${order.products}”> 中的order.products则存放是的一个订单中的所有商品,而product则是每个商品的数据。

    另外,product后面的status存放的是迭代相关的信息:

    • index:第几次循环,下标从0开始

    • count:第几次循环,下标从1开始

    • size:一共循环几次

    • current:当前正在循环的迭代变量

    另外th:if=”${status.index == 0}”相当于判断,如果status.index == 0,则会显示该标签

    四.  引入页面中公共部分

    假如我们的网站中有一百个页面,这一百个页面肯定会有一些公共的部分内容是相同的,比如菜单、网站底部信息等等,在jsp中我们可以使用静态导入或者动态导入的方法来解决这个问题,相应的,thymeleaf也为我们提供了插入或者替换两种方式来将页面中的公共部分引入

    这里以任意界面的菜单为例:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页 - 电子商城</title>  
    
    </head>
    <body>
    <header >
        <div class="container">
            <nav class="navbar navbar-expand-lg navbar-light bg-light rounded">
                <a class="navbar-brand" href="#">电子商城1</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample09" aria-controls="navbarsExample09" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
    
                <div class="collapse navbar-collapse" id="navbarsExample09">
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item active">
                            <a class="nav-link" href="#">首页<span class="sr-only">(current)</span></a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">电脑</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">手机</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">平板</a>
                        </li>
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" href="#" id="dropdown09" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">张小三</a>
                            <div class="dropdown-menu" aria-labelledby="dropdown09">
                                <a class="dropdown-item" href="#">个人中心</a>
                                <a class="dropdown-item" href="#">购物车</a>
                                <a class="dropdown-item" href="#">收货地址</a>
                                <a class="dropdown-item" href="#">我的订单</a>
                                <a class="dropdown-item" href="#">退出登录</a>
                            </div>
                        </li>
                    </ul>
                    <form class="form-inline my-2 my-md-0">
                        <input class="form-control" type="text" placeholder="Search" aria-label="Search">
                        <input type="submit" class="btn btn-outline-secondary" value="搜索">
                    </form>
                </div>
            </nav>
        </div>
    
    </header>
    <main role="main" class="container">
        ......
    </main>
    <footer class="fdb-block footer-small">
      ......
    </footer>
    </body>
    </html>

    在上述代码中,<header>标签的代码即为网站的菜单,也就是每个页面都会拥有的公平部分,我们可以新建一个专门用于存放公共部分的页面的_common.html,然后将header标签中的内容存入:

    _common.html:

    <header th:fragment="head">
        <div class="container">
            <nav class="navbar navbar-expand-lg navbar-light bg-light rounded">
                <a class="navbar-brand" href="#">电子商城</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample09" aria-controls="navbarsExample09" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
    
                <div class="collapse navbar-collapse" id="navbarsExample09">
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item active">
                            <a class="nav-link" href="#">首页<span class="sr-only">(current)</span></a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">电脑</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">手机</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">平板</a>
                        </li>
                        <li class="nav-item dropdown">
                            <a class="nav-link dropdown-toggle" href="#" id="dropdown09" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">张小三</a>
                            <div class="dropdown-menu" aria-labelledby="dropdown09">
                                <a class="dropdown-item" href="#">个人中心</a>
                                <a class="dropdown-item" href="#">购物车</a>
                                <a class="dropdown-item" href="#">收货地址</a>
                                <a class="dropdown-item" href="#">我的订单</a>
                                <a class="dropdown-item" href="#">退出登录</a>
                            </div>
                        </li>
                    </ul>
                    <form class="form-inline my-2 my-md-0">
                        <input class="form-control" type="text" placeholder="Search" aria-label="Search">
                        <input type="submit" class="btn btn-outline-secondary" value="搜索">
                    </form>
                </div>
            </nav>
        </div>
    </header>

    其中th:fragment=”head” 相当于为该部分代码起了一个别名,用来唯一标识。同样的,如果需要定义页面中的其他公共部分,只需要在_common.html中添加多个th:fragment=”xxx”即可,无需为每公共部分单独定义一个文件。

    接下来演示使用插入和替换两种方式在每个页面使用这些公共部分

    1.  插入

    <header  th:insert="_common :: head">
    <!--   header中的原内容也可以不删除-->
    </header>

    2.  替换

    <header th:replace="_common :: head">
    <!--   header中的原内容也可以不删除-->
    </header>

    其中_common即为_common.html的文件名,而head即为要导入的公平部分的唯一标识。

    另外,如果采用插入的方式,那么在_common.html中,需要将

    <header th:fragment="head">
        <div class="container">
        ....
        </div>
    </header>
    修改为:
    <div th:fragment="head" class="container">
        ....
    </div>

    这样的做的原因是因为插入会将_common.html中的代码原封不等的插入进header标签内,所以如果不修改,则页面的代码会变成:

    <header >
        <header>   
            <div class="container">   
                .....
            </div>   
        </header>
    </header>

    而替换则不需要考虑上述问题。另外还有th:include这种导入方式,但是官方建议在3.0之后不推荐使用

  • 0
  • 0
  • 0
  • 3.9k
  • 请登录之后再进行评论

    登录
    单栏布局 侧栏位置: