专栏首页品茗ITSpringBoot入门建站全系列(八)集成模板引擎(thymeleaf)渲染页面
原创

SpringBoot入门建站全系列(八)集成模板引擎(thymeleaf)渲染页面

SpringBoot入门建站全系列(八)集成模板引擎(thymeleaf)渲染页面

说到页面渲染,这里不得不给大家科普一下:

    1. html文件负责显示页面,后台数据可以通过ajax方式获取,如果数据完全使用ajax获取,这样页面和后端就没什么联系,这时候就可以成为前后端分离。
    1. jsp页面是一种页面渲染方式,它是对jsp文件解析后,将后台数据填充到html页面并返回到前端,这种方式是非前后端分离的,所以,脱离了容器,jsp页面啥都不是,根本打不开。jsp可以写java代码,可以写逻辑,功能很强大,但是也是它逐渐被抛弃的原因。
    1. 模板引擎, 目前是比较流行的一种写法,或许你会问:前后端分离已经很好用了,为啥还要用模板引擎?原因是这样的,前后端分离的页面,是不经过容器控制的,所以安全框架不能对它进行过滤,这是其一,其二、前后端分离的页面,数据是ajax异步获取的,所以首次打开页面时(比如爬虫爬取页面,未执行js等),获取到的只是一些html元素,没有数据填充,而模板引擎可以在获取html文件时就将数据填充进去。
    1. 模板引擎也是依赖于后端容器,页面中的内容使用标签进行替换。

本文不讲前后端分离,先讲下模板引擎,Springboot支持很多模板引擎,thymeleaf算是比较好用的一种。

一、Maven依赖

需要引入spring-boot-starter-thymeleaf和spring-boot-starter-web。

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.cff</groupId>
		<artifactId>springbootwork</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>Thymeleaf</artifactId>
	<name>Thymeleaf</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

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

父pom管理了所有依赖jar包的版本,地址: https://www.pomit.cn/spring/SpringBootWork/pom.xml

二、thymeleaf配置

SpringBoot对thymeleaf做了很好的整合,隐藏了大部分细节,所以,我们只需要在Springboot的配置文件(一般是用application.properties)中加入以下配置即可:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

这里,

  • spring.thymeleaf.cache是对页面不缓存,这样修改了模板文件,立即生效。
  • spring.thymeleaf.prefix指明了模板文件的地址
  • spring.thymeleaf.suffix 指明了模板文件后缀。

另外三个参数就不说了,顾名思义。

三、thymeleaf的模板页面

这里建了一个模板文件detail.html文件。乍一看,它和html没啥区别,其实区别真不大,只是里面有thymeleaf的标签。

里面的th:开头的都是thymeleaf的标签,如th:href、th:onclick、th:text等。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="keywords" th:attr="content=${data.data.catory}"/>
    <meta name="description" th:attr="content=${data.data.title}"/>
    <title th:text="${data.data.title}">品茗IT-博文详情</title>

    <!-- CSS  -->
    <link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
    <link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
	
	<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
    <style>

        nav ul a,
        nav .brand-logo {
            color: #444;
        }
		.side-nav{
			max-width: 50%;
		}
        p {
            line-height: 2rem;
        }

        .button-collapse {
            color: #26a69a;
        }

        .parallax-container {
            min-height: 380px;
            line-height: 0;
            height: auto;
            color: rgba(255,255,255,.9);
        }
        .parallax-container .section {
            width: 100%;
        }

        @media only screen and (max-width : 992px) {
            .parallax-container .section {
                position: absolute;
                top: 40%;
            }
            #index-banner .section {
                top: 10%;
            }
            
        }

        @media only screen and (max-width : 600px) {
            #index-banner .section {
                top: 0;
            }
            
            .collection .collection-item.avatar {
			    padding-left: 15px;
			}
        }

        .icon-block {
            padding: 0 15px;
        }
        .icon-block .material-icons {
            font-size: inherit;
        }

        footer.page-footer {
            margin: 0;
        }
        
        .token.punctuation {
		    color: #999;
		}
		pre {
		    -moz-osx-font-smoothing: initial;
		    -webkit-font-smoothing: initial;
		    background-color: #f8f8f8;
		    font-family: 'Roboto Mono', Monaco, courier, monospace;
		    line-height: 1.5rem;
		    margin: 1.2em 0;
		    overflow: auto;
		    padding: 0 1.4rem;
		    position: relative;
		    word-wrap: normal;
		}
		pre > code {
		    -moz-osx-font-smoothing: initial;
		    -webkit-font-smoothing: initial;
		    background-color: #f8f8f8;
		    border-radius: 2px;
		    color: #525252;
		    display: block;
		    font-family: 'Roboto Mono', Monaco, courier, monospace;
		    font-size: 0.8rem;
		    line-height: inherit;
		    margin: 0 2px;
		    max-width: inherit;
		    overflow: inherit;
		    padding: 2.2em 5px;
		    white-space: inherit;
		}
		
		.dialog{
            position: relative;
            display: inline-block;
            max-width: 200px;
            padding: 4px 8px;
            border-radius: 2px;
            background-color: #dddddd;
            line-height: 20px;
            font-size: 14px;
        }
        .u-tri:before{
            position: absolute;
            left: 77px;
            top: 11px;
            content: '';
            border-top: 8px solid transparent;
            border-bottom: 8px solid transparent;
            border-right: 12px solid transparent;
            border-left: 12px solid #dddddd;
        }
		.question{
			font-size: 22px;
			font-weight: bold;
			color: #112f6b;
		}
		.question-index{
			font-size: 10px;
			color: grey;
		}
		.btn{
			padding: 0 1rem;
			color:#2196f3 ;
		}
		.authorName{
			padding-left:10px;
			font-size: 14px;
			color: #64b5f6 ;
			cursor: pointer;
		}
		.createTime{
			padding-left:10px;
			font-size: 12px;
			color: grey;
		}
		.catory{
			padding-left:5px;
			font-size: 14px;
			color: grey;
		}
		.catory:before{
			content:"【";
		}
		.catory:AFTER{
			content:"】";
		}
		.outer {
			overflow: hidden;
			text-overflow: ellipsis;
			/*设置成弹性盒子 */
			display: -webkit-box;
			/*显示的个数 */
			-webkit-line-clamp: 2;
			/* 属性规定框的子元素应该被水平或垂直排列。 */
			-webkit-box-orient: vertical;
		}
		.editBtn{
			padding-right:40px;
		}
		.form-control {
		    display: block;
		    width: 100%;
		    height: 50px;
		    padding: 6px 12px;
		    font-size: 14px;
		    line-height: 1.42857143;
		    color: #555;
		    background-color: #fff;
		    background-image: none;
		    border: 1px solid #ccc;
		    border-radius: 4px;
		    -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
		    box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
		    -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
		    -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
		    transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
		}
		.comment-form{
			height: 100px;
		}
		.comments-list-area{
			padding: 20px 10px;
		}
		.comments-area{
			padding: 20px 10px;
		}
		.important-text{
			font-weight: bold;
			color:#112f6b;
		}
		.replyCommentBtn{
			margin-left:10px;
			color:light-blue;
		}
		.replyCommentBtn:hover{
			color:#1565c0;
		}
		.fixed-menu{
			position: relative;
		}
    </style>
</head>
<body class="amber lighten-5">
<!-- header end -->
<div class="fixed-menu hide-on-med-and-down">
    <ul id="tree" class="ztree" style='width:100%'>
		
    </ul>
</div>
<!-- content -->
<div class="section no-pad-bot " >
	<div class="container">
		<div class="card-panel" style="min-height:90vh;padding: 0px">
			<ul class="collection" id="issueList" style="border: none">
			    <li class="collection-item avatar">
			    	<div class="row">
			    		<div class="col s12"><span class="question" id="title"  th:text="${data.data.title}">Spring序列化布尔类型错误</span></div>
			    	</div>
			    	<div class="row">
			    		<span class="catory" id="catory"  th:text="${data.data.catory}">【软件使用】</span>
			    		<span class="createTime" id="createTime" th:text="${#dates.format(data.data.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-03-06</span>
			    		<span class="createTime" id="openStatus" th:text="${data.data.open}==0?'公开':'私有'">未知</span>
			    		
			    		<a class="authorName" id="authorName" th:href="'/page/userData.html?user=' + ${data.data.author}" th:text="${data.data.author}">匿名</a>
			    		<span style="padding-left:20px;" class="createTime">点赞:<span class="starNum" id="starNum" th:text="${data.data.star}">1</span></span>
			    		
			    		<a id="editBtn" class="editBtn right" th:href="'/page/issue/editIssue.html?id=' + ${data.data.id}" th:if="${#strings.equals(data.remark,data.data.author)}">编辑</a>
			    	</div>
			    	<div class="divider"></div>
			    	<div class="row" style="margin-top: 2rem;">
			    		<div class="col s12">
			    			<span class="answer markdown-section" id="content" ></span>
			    		</div>
			    	</div>
			    	
			    </li>
			   
			</ul>
			<div class="divider"></div>
			<div class="center" style="padding-top: 20px;;left: 45%;padding-bottom: 20px;">
		    	<a id="starBtn" th:onclick="'javascript:issueStar(' + ${data.data.id} + ');'" class="waves-effect waves-light btn light-blue lighten-5"><i id="star_icon" class="material-icons right">favorite_border</i>赞</a>
	    	</div>
		</div>
	</div>
</div>

<!-- footer start -->
<footer class="page-footer teal">
    <div class="container">
        <div class="row">
            <div class="col l8 s12">
                <h5 class="white-text">网站简介.</h5>
                <p class="grey-text text-lighten-4">
                    本站提供多领域的技术解决方案,包括web网站、小程序、开源项目、h5等。
                </p>
            </div>
            

        </div>
    </div>
    <div class="footer-copyright">
        <div class="container">
            Copyright (c) 2018-present, 陈付菲.
            <a class="grey-text text-lighten-4 right" ></a>
        </div>
    </div>
</footer>
<!-- footer end -->

</body>

<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
<script th:inline="javascript">
    $(function () {
    	
       	
    });
    
</script>

</html>

四、thymeleaf的页面控制器

使用模板引擎,需要我们自己控制ModelAndView。这里的ModelAndView的viewName是detail,结合thymeleaf的配置,就是找spring.thymeleaf.prefix + viewName + spring.thymeleaf.suffix 指定的这个文件。

package com.cff.springbootwork.thymeleaf.web;

import java.util.Date;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.cff.springbootwork.thymeleaf.dto.FQuestionInfo;
import com.cff.springbootwork.thymeleaf.dto.ResultCode;
import com.cff.springbootwork.thymeleaf.dto.ResultModel;

@RestController
@RequestMapping("/thymeleaf")
public class ThymeleafRest {
	@RequestMapping("/page")
	public <FQuestionIndex> ModelAndView getThymeleaf() {
		ModelAndView modelAndView = new ModelAndView("detail");

		try {
			FQuestionInfo fQuestionInfo = new FQuestionInfo();
			fQuestionInfo.setAuthor("cff");
			fQuestionInfo.setCatory("大爷");
			fQuestionInfo.setId(123123123L);
			fQuestionInfo.setOpen(1);
			fQuestionInfo.setStar(123);
			fQuestionInfo.setTitle("我就是一个测试模板引擎的实体而已。");

			fQuestionInfo.setCreateTime(new Date());

			ResultModel retOk = ResultModel.ok(fQuestionInfo);
			modelAndView.addObject("data", retOk);
		} catch (Exception e) {
			e.printStackTrace();
			modelAndView.addObject("data", new ResultModel(ResultCode.CODE_00004));
		}

		return modelAndView;
	}
}

这里面,FQuestionInfo 是保存数据的一个实体,ResultModel是一个统一返回的实体,其实可以写成一个实体,都是作为返回数据的。

modelAndView使用addObject("data", retOk);将数据传递给viewName指定的模板文件,解析后返回一个html文件给浏览器。

页面如下:

在这里插入图片描述

五、过程中使用到的实体

详细完整的实体,可以访问品茗IT-博客《SpringBoot入门建站全系列(八)集成模板引擎(thymeleaf)渲染页面》

快速构建项目

Spring组件化构建

喜欢这篇文章么,喜欢就加入我们一起讨论SpringBoot技术吧!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringBoot入门建站全系列(三十)Mybatis多数据源进行数据库操作

    这就牵扯到分布式事务控制,本篇只是讲述多数据源的使用,下一篇再讲述如何使用分布式事务控制中间件。

    品茗IT
  • SpringBoot入门建站全系列(十三)本地缓存的使用(Ehcache和caffeine的使用)

    本地缓存,就是使用应用内使用本地内存将数据暂缓存储,一般数据库的查询如果不怎么改动,可以用本地缓存暂存。

    品茗IT
  • SpringBoot入门建站全系列(二十四)使用Sharding-JDBC进行分库分表

    一个系统最初的线上业务量并不会很大,比如说单库的数据量在百万级别以下(事实上千万级别以下都还能支撑),那么MySQL的单库即可完成任何增/删/改/查的业务操作。...

    品茗IT
  • PHP GD库解析一张简单图片并输出

    CrazyCodes
  • 大数据文摘陪你过七夕|美国分会场完美收官 巴黎北京上海继续约

    大数据文摘
  • 三分钟快速上手码云 JavaDoc

    码云Gitee
  • Runtime再理解

    Objective-C、Java、Swift等高级语言,其可读性很强,但是并不能直接被机器识别,所以就需要将这些源代码编译成相对应的机器语言(比如汇编语言),最...

    拉维
  • Swift讲解专题十三——下标访问 原

            在以前的博客中,讨论过在Objective-C中,通过下标的方式访问自定义数据模型的方法。Objective-C中主要是通过实现一系列方法来使自...

    珲少
  • 腾讯提超强少样本目标检测算法,公开1000类检测训练集FSOD | CVPR 2020

    不同于正常的目标检测任务,few-show目标检测任务需要通过几张新目标类别的图片在测试集中找出所有对应的前景。为了处理好这个任务,论文主要有两个贡献:

    AI科技大本营
  • 如何向Hive表加载数据

    使用追加的方式将test_user表中id大于3并且小于5的数据插入到my_table表中,执行结果如下:

    Fayson

扫码关注云+社区

领取腾讯云代金券