Spring Template

Thymeleaf 入门

Web 开发离不开动态页面的开发,很早以前企业主要使用 JSP 技术来开发网页,随着技术的升级更替,目前来说最主流的方案是:Thymeleaf,Thymeleaf 是一个模板框架,它可以支持多种格式的内容动态渲染非常强大,它天然和 HTML 是相融合的,所以对于前端工程师来说它也是易于理解的。

还有一个重要的原因是 Spring 选择 Thymeleaf 作为默认模板方案,Spring 的选择自然让这项技术成为最优先采用的方案

Web 工程师基本上也必须要掌握一门模板框架的,要不然没有办法完成动态网页的开发工作,这也是软件开发最基本的要求了

举个例子,你就明白模板是个啥了

如上图,通过模板引擎可以把 Java 对象数据+模板页面动态的渲染出一个真实的 HTML 页面来。

上面的例子中,如果 name 变成其他歌单的名称,那么页面渲染后也自然就会变成新的歌单名称

模板引擎在所有的 Web 编程语言里都有类似的方案的,所以大家掌握住一个框架,以后在去学习其他的也很容易。关键先理解它的机制,简单的来说就是数据+模板+引擎渲染出真实的页面

如何初始化 Thymeleaf

添加 Maven 依赖

由于现代的工程都是基于 Spring Boot 来搭建的,所以我们使用 Thymeleaf 也非常简单。我们只需要在工程的pom.xml中添加依赖即可

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
数据传递

Spring MVC 把页面数据层封装的非常完善,只需要我们在方法参数里引入一个Model对象,就可以通过这个 Model 对象传递数据到页面中了。

我们首先正确的导入这个 Model 类

import org.springframework.ui.Model;

为了更好的演示数据,我们把之前的歌单服务也集成了过来

@Controller
public class SongListControl {@Autowiredprivate 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

所以上面的 return "songList"; 其实会去查找 src/main/resources/templates/songList.html文件,系统会自动去匹配后缀的,所以你不需要写成return "songList.html";

Thymeleaf 模板文件也是以 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" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><link rel="stylesheet" href="/css/songList.css" /><title>豆瓣歌单</title></head><body><h1 th:text="${songList.name}"></h1></body>
</html>

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

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

thymeleaf 变量、语法接下来会学习,这里不用太纠结

很多技术学习的难度都在与环境配置,各种小问题都会导致环境错误从而导致工程不能运行了,所以当你遇到这样、那样问题的时候,就需要一个个的去排查。

接下来我们会深入学习 Thymeleaf 框架,会以这个工程为例

特别提醒

只要在工程的 src/main/resources/templates 下面放置了文件,就表示需要使用 Thymeleaf ,这些文件就是 模板,无论在模板中是否写了变量(${songList.name})。这大家要注意理解规范。

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

只要用到了 Thymeleaf 就 必须 在工程的 pom.xml 文件中增上述依赖。忘记加了就会出错,而且可能不知道为什么出错,所以大家务必做到遵守规范,有问题先检查是否缺少依赖。

Thymeleaf 变量

搞定工程环境之后,我们就开始学习模板语法

Thymeleaf 模板语法非常强大,相当于是一门动态编程语言,所以很多语言的特性它都支持,比如说变量、循环、条件等

在我们上节中使用的 th:text="${songList.name}" 这个就是使用变量技术

模板变量

由于 Thymeleaf 是完全兼容 HTML 的,所以为了不破坏 HTML 结构,Thymeleaf 采用了自定义 HTML 属性的方式来生成动态内容

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

th:text 语法的作用就是会动态替换掉 html 标签的内部内容。

<span th:text="${msg}">Hello</span>

这段代码的执行结果就是用 msg 变量值替换了 span 标签内的 Hello 字符串,比如说 msg 变量值是你好,那么代码渲染的结果是

<span>你好</span>

再来看看如何读取变量,上面th属性内的${msg} 这个语法就是表示获取模板中的变量 msg,请注意这个语法格式(它是固定的)${xxx}

上下文这个术语在编程里用的比较多,一般泛指在某个具体的实例里,比如说模板上下文,指的是这个模板运行的实例

一般情况下,模板的变量都是会存放在模板上下文中,所以我们如果想要调用变量,就需要先设置变量到模板上下文中去,你只需要像上节课一样model.addAttribute("songList",songList);就可以完成上下文变量的设置

比如,我们这个例子

import org.springframework.ui.Model;@Controller
public class DemoControl {@RequestMapping("/demo")public String index(Model model){String str = "你好";model.addAttribute("msg",str);return "demo";}
}

大家仔细看看这个 model.addAttribute("msg",str);这个方法

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

模板语言还可以支持复杂对象的输出,我们完全可以使用 . 把属性调用出来,比如我们的歌单对象 SongList

import org.springframework.ui.Model;@Controller
public class DemoControl {@RequestMapping("/demo")public String index(Model model){SongList songList = new SongList();songList.setId("0001");songList.setName("爱你一万年");model.addAttribute("sl",songList);return "demo";}
}

我们在模板中可以通过th:text="sl.name"th:text="sl.id" 分别得到 name、id 值

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8" /></head><body><span th:text="${sl.id}"></span><span th:text="${sl.name}"></span></body>
</html>

如果对象包含对象,还是可以用 . 一直点出来的,前提是这个对象是 POJO 类哦

后端-java代码要求

完成 SongListControl 的歌单方法,请求 /songlist 时:

  • 根据参数 id 从歌单服务 songListService 查询歌单对象,并传递给页面。
  • 传递信息标题 歌单信息 给页面。
package fm.douban.app.control;import java.util.*;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import fm.douban.app.model.*;
import fm.douban.app.service.*;
import org.springframework.ui.Model;@Controller
public class SongListControl {@Autowiredprivate SongListService songListService;@RequestMapping("/songlist")public String index( @RequestParam("id") String id ,Model model) {// 根据参数 id 从歌单服务查询歌单对象SongList songList = songListService.get(id);// 将歌单对象传递给页面model.addAttribute("songList", songList);// 传递信息标题"歌单信息"给页面model.addAttribute("title", "歌单信息");return "songList";}
}

Thymeleaf 循环语句

在模板当中,for 循环和变量的组合是最常见的,比如说歌单的歌曲列表,如果我们想显示出来,那就需要借助循环

Thymeleaf 的 for 循环也是使用标签属性来完成的,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";}

你可以自己尝试改一下上面的测试代码数据,再运行看看。学模板语法就是多写点就会了,这属于熟能生巧的技术

Thymeleaf 除了支持 List 数据类型,还可以支持数组、Map,你可以当成 Java 的 for 语句一样,集合类都支持的

打印列表的索引值

我们经常看到有些列表需要显示当前行数,这个时候,就需要借助 th:each语句的另外一种写法

<ul th:each="song,it: ${songs}"><li><span th:text="${it.count}"></span><span th:text="${song.name}"></span></li>
</ul>

运行后你会看见如下的

现在我们来看看上面的代码,大家仔细看看th:each="song,it: ${songs}",你会发现多了一个,it这个 it 是作为可选参数出行,如果定义了就可以通过这个 it 对象来获取更多关于统计的需求,具体参数如下

  • it.index

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

  • it.count

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

  • it.size

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

  • it.current

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

  • it.even/odd

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

  • it.first

    布尔值,当前循环是否是第一个

  • it.last

    布尔值,当前循环是否是最后一个

模板中的布尔值需要结合条件语句来处理的

上面显示的都是单层的循环,如果你的数据对象是有多级的,也可以嵌套循环,我们遇到问题的时候再处理吧

在页面上打印所有歌单的信息

后端-java代码要求

完成 SongListControl 的歌单方法,请求 /songlists 时:

  • 从歌单服务 songListService 查询全部歌单(all()方法)对象,并传递给页面。
    • all() 方法已经实现了。直接调用即可。
  • 传递信息标题 所有歌单 给页面。

前端-页面代码要求

补充完整 sls.html 页面,展示所有歌单。其中 body 区域的输出格式如下:

  <body><h2>所有歌单</h2><ul><li><span>1</span><div>林子祥:宝刀不老,初代歌神</div><span>锅炉不是锅</span></li><li><span>2</span><div>【华语】有些歌,长大了才能听懂</div><span>九月九</span></li></ul></body>

页面上的内容都是根据后端数据渲染出来的,不能在页面上写死。

@Controller
public class SongListControl {@Autowiredprivate SongListService songListService;@RequestMapping("/songlist")public String index( @RequestParam("id") String id ,Model model) {SongList songList = songListService.get(id);model.addAttribute("songList", songList);model.addAttribute("title", "歌单信息");  return "songList";//"xxx"对应的内容为.html型的}@RequestMapping("/songlists")public String songlists(Model model) {// 从歌单服务查询全部歌单对象(从歌单服务 songListService 查询全部歌单(all()方法)对象,并传递给页面)List<SongList> songLists = songListService.all();// 将全部歌单对象传递给页面model.addAttribute("songLists", songLists);// 传递信息标题"所有歌单"给页面model.addAttribute("title", "所有歌单");return "sls";}
}

 Thymeleaf 表达式

作为一个模板语言,必不可少的能力就是动态, Thymeleaf 表达式 对于动态数据处理更为方便

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

  • 字符串处理
  • 数据转化

字符串处理

我们在很多网站上都会看到视频时间的显示效果是这样的00:00/45:00,大家应该很容易明白这个代表的就是这个视频从 0 分 0 秒开始,总共 45 分钟。在 Thymeleaf 中这种显示就需要借助+就可以完成字符串拼接了

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

这里需要注意的是'00:00/',我们使用'包围住 00:00/ 这个文本的作用在于把这个文本变成 Java 字符串,两个字符串就可以使用+拼接成新的字符串了,这和 Java 的字符串拼接是一样的。 Control 代码我们也调整一下

  @RequestMapping("/demo")public String index(Model model){String totalTime = "45:00";model.addAttribute("totalTime",totalTime);return "demo";}

如果我们不使用'包围住 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)
如何看模板错误

我们在使用模板开发的时候,经常会遇到写错的情况,比如上面这种,遇到错误不要慌仔细看错误是可以分辨出来的

上面这个错误其实已经告诉了我们:demo.html 这个模板编译错误,在第 7 行,第 9 列遇到了 "00:00/+${totalTime}" 这个错误表达式

字符串拼接优化

Thymeleaf 做字符串拼接还做了优化工作,我们可以使用 上面的代码你还可以这样|包围住字符串,这样就不需要在文字后面附加 '...' + '...'

<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 类的。如果不了解也没有关系的,搜索一下看看文档就知道啦

我们一般使用 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>

Java 代码如下

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

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

  @RequestMapping("/demo")public String index(Model model){LocalDateTime dateVar = LocalDateTime.now();model.addAttribute("dateVar",dateVar);return "demo";}
strings

除了日期方法,#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)}

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

这些函数大家自己可以尝试去练习一下,基本上这些多做一些产品开发就非常熟练了

完整的内置对象,大家了解就可以了,用到的时候自己查查文档,在具体的使用场景中我们也会逐步介绍的,因为一下子我们也记不住那么多的内容

  • #messages
  • #urls/uris
  • #conversions
  • #dates
  • #calendars
  • #numbers
  • #strings
  • #objects
  • #bools
  • #arrays
  • #lists
  • #sets
  • #maps
  • #aggregates
  • #ids

各内置对象包含哪些方法可以点此看文档, 有兴趣的也可以点此查看官方文档

内联表达式

尽管我们使用 th:text 也比较方便,但是有些时候可能我们还是更喜欢直接把变量写在 HTML 中,比如这种写法

<span>Hello [[${msg}]]</span>

上面的[[变量]]这种格式就是内联表达式,支持我们直接在 HTML 中调用变量

  @RequestMapping("/demo")public String index(Model model){String msg = "丫丫";model.addAttribute("msg",msg);return "demo";}

现在我们用新语法执行日期函数看看

<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:标签哦

要求:

 <body><h2>所有歌单</h2><ul><li><span>序号:1</span><div>歌单名称:林子祥:宝刀不老,初代歌神</div><span>锅炉不是锅 创建于 2019/05/07</span></li><li><span>序号:2</span><div>歌单名称:【华语】有些歌,长大了才能听懂</div><span>九月九 创建于 2019/06/04</span></li></ul></body>

内联式实现:

<body>
<h2 th:text="${title}"></h2>
<ul th:each="sl,it : ${sls}"><li><span>序号:[[${it.count}]]</span><div>歌单名称:[[${sl.name}]]</div><span>[[${sl.author}]] 创建于 [[${#temporals.format(sl.publishedDate,'yyyy/MM/dd')}]]</span></li>
</ul>
</body>

Thymeleaf 条件语句

动态页面必然会遇到 if/else 的情况,比如说网页中显示用户性别

  • 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>

配置一下 Java 数据

  @RequestMapping("/demo")public String index(Model model){User user = new User();user.setId("0001");user.setName("范闲");user.setSex("male");model.addAttribute("user",user);return "demo";}

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

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

现在我们结合循环语句显示用户列表看看

<div><li th:each="user : ${users}"><span>[[${user.name}]]</span><span th:if="${user.sex == 'male'}">男</span><span th:unless="${user.sex == 'male'}">女</span></li>
</div>

配置一下 Java 数据

  @RequestMapping("/demo")public String index(Model model){List<User> users = new ArrayList<>();User user = new User();user.setId("0001");user.setName("范闲");user.setSex("male");users.add(user);user = new User();user.setId("0002");user.setName("司理理");user.setSex("female");users.add(user);user = new User();user.setId("0003");user.setName("庆帝");user.setSex("male");users.add(user);user = new User();user.setId("0004");user.setName("海棠朵朵");user.setSex("female");users.add(user);model.addAttribute("users",users);return "demo";}

strings 逻辑判断

在很多时候,我们还会借助#strings这个内置对象来做逻辑判断和数据处理,比如

isEmpty

检查字符串变量是否为空(或者为 null),在检查之前会先执行 trim() 操作,去掉空格

${#strings.isEmpty(name)}

数组也适用 isEmpty

${#strings.arrayIsEmpty(name)}

集合类也适用 isEmpty

${#strings.listIsEmpty(name)}

现在我们可以测试一下

  @RequestMapping("/demo")public String index(Model model){String str1 = "a";String str2 = "";String str3 = "  ";String str4 = null;model.addAttribute("str1",str1);model.addAttribute("str2",str2);model.addAttribute("str3",str3);model.addAttribute("str4",str4);return "demo";}

模板代码如下

<p th:if="${#strings.isEmpty(str1)}">String str1 = "a";</p>
<p th:if="${#strings.isEmpty(str2)}">String str2 = "";</p>
<p th:if="${#strings.isEmpty(str3)}">String str3 = "  ";</p>
<p th:if="${#strings.isEmpty(str4)}">String str4 = null;</p>

运行完,大家应该看见页面只有 String str1 = "a"; 没有显示出来,因为其他的几种都是匹配了 isEmpty=true

contains

检查字符串变量是否包含片段

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

现在我们可以测试一下

  @RequestMapping("/demo")public String index(Model model){String str1 = "我是中国人";model.addAttribute("str1",str1);return "demo";}

模板代码如下

<p th:if="${#strings.contains(str1,'人')}">匹配到人这个字啦</p>

OK,我想你应该掌握到了这种用法了,#strings 判断语法还有几个常用的方法,大家可以自己测试一下:

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

    先忽略大小写字母,然后去判断是否包含指定的字符串

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

    判断字符串是不是以 abc 开头的

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

    判断字符串是不是以 abc 结束的

#strings 的字符串操作函数

除了字符串判断语句外,#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)}

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

这些函数大家自己可以尝试去练习一下,基本上这些多做一些产品开发就非常熟练了

要求:

 代码实现:

<body><h2 th:text="${title}"></h2><ul><li th:each="sl,it: ${sls}"><div th:if="${it.odd}" class="row-odd"><span>序号:[[${it.count}]]</span><div>歌单名称:[[${sl.name}]]</div><span>[[${sl.author}]] 创建于 [[${#temporals.format(sl.publishedDate, 'yyyy/MM/dd')}]]</span></div><div th:if="${it.even}" class="row-even"><span>序号:[[${it.count}]]</span><div>歌单名称:[[${sl.name}]]</div><span>[[${sl.author}]] 创建于 [[${#temporals.format(sl.publishedDate, 'yyyy/MM/dd')}]]</span></div></li></ul>
</body>

sls.css实现:

.row-odd {background-color: #ffffff;
}.row-even {background-color: #dfdfdf;
}

Thymeleaf 表单

在接下来的课程中,我们逐步完成一个简易版的图书管理系统进而更进一步的学习和掌握 Spring 以及 Thymeleaf 框架

首先我们从添加图书开始

图书模型

图书大家都很熟悉,我们可以抽象一下图书变成 Java 类

上面的 UML 写成 Java 代码就是

public class Book{// 主键private long id;// 图书的名称private String name;// 图书的作者private String author;// 图书的描述private String desc;// 图书的编号private String isbn;// 图书的价格private double price;// 图书的封面图片private String pictureUrl;// 省略 getter、setter
}

有了这些信息我们就可以通过 Java 对象来描述一本书了

细心的你可能会发现在 Book 类中,我们把 id 主键的类型设置为 long 了,这是因为 long 类型的 id 更易于搜索引擎。我们如果期望被搜索引擎能够关注到产品,那么就可以把 id 设置为 long,否则还是用 String,因为 long 很容易被机器猜到,所以很容易被爬取数据(既是优点又是缺点,由产品特性来决定)

页面开发

首先,我们先创建一个名为addBook.html的 thymeleaf 模板

<form><div><label>书的名称:</label><input type="text" /></div><div><label>书的作者:</label><input type="text" /></div><div><label>书的描述:</label><textarea></textarea></div><div><label>书的编号:</label><input type="text" /></div><div><label>书的价格:</label><input type="text" /></div><div><label>书的封面:</label><input type="text" /></div><div><button type="submit">注册</button></div>
</form>

为了能够访问到这个页面,我们还需要配置一下 Spring Control

package com.bookstore.control;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;@Controller
public class BookControl {// 当页面访问 http://localhost:8080/book/add.html 时// 渲染 addBook.html 模板@GetMapping("/book/add.html")public String addBookHtml(Model model){return "addBook";}
}

保存书籍

上面我们开发了一个简单的表单页面,现在我们在新增一个 control 来处理书籍保存的逻辑。

package com.bookstore.control;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;import java.util.*;import com.bookstore.model.*;@Controller
public class BookControl {//缓存所有书籍数据private static List<Book> books = new ArrayList<>();@GetMapping("/book/add.html")public String addBookHtml(Model model){return "addBook";}@PostMapping("/book/save")public String saveBook(Book book){books.add(book);return "saveBookSuccess";}}

@PostMapping 和 @GetMapping 不同点在于只接收 http method 为 post 请求的数据,它的包路径和 GetMapping 注解类一样

我们在这个 saveBook 方法里做了一个简单的处理,接收 Book 对象数据并存储到 books 对象里

我们还新增一个 templates/saveBookSuccess.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" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>添加书籍</title>
</head><body><h2>添加书籍成功</h2>
</body></html>

form 表单

我们还需要修改一下 html form,需要指定 form 的 action 属性值就是后端的请求路径,由于我们写的是/开头的,浏览器会自动把请求地址识别为http://domain/user/reg,如果本地开发这个 domain 可能是 localhost:8080

一般情况下,我们都会把 Html 表单的 method 设置为 post,这样可以保证数据传输安全,这样 Spring Mvc 就需要接收 Post 请求,现在我们完善这个后端代码

除了 form 属性调整,还需要修改 input 的 name 属性,属性和 Book 类的属性名要一致哦

<!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" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>添加书籍</title>
</head><body><h2>添加书籍</h2><form action="/book/save" method="POST"><div><label>书的名称:</label><input type="text" name="name"></div><div><label>书的作者:</label><input type="text" name="author"></div><div><label>书的描述:</label><textarea name="desc"></textarea></div><div><label>书的编号:</label><input type="text" name="isbn"></div><div><label>书的价格:</label><input type="text" name="price"></div><div><label>书的封面:</label><input type="text" name="pictureUrl"></div><div><button type="submit">注册</button></div></form>
</body></html>

Spring Validation(一)

现在我们继续完善了书籍的添加逻辑,在实际的工作中对于数据的保存是离不开数据验证的,比如说 name 必须要输入,isbn 必须要输入等等校验规则,Spring 对于数据验证支持的也非常好,我们可以借助 Spring Validation 来处理表单数据的验证

JSR 380

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

基本上我们用到的很多 Java 框架都是某个 JSR 的实现者,大家现在有个概念就好。当然等你技术有一定深度的时候,你可以尝试去阅读、理解 JSR 规范

JSR 380 其实就是 Bean Validation 2.0 ,这个就是 Bean 验证的规范,这里的Bean 就是我们一直在说的实例化后的 POJO类,比如前面的 Book。JSR 380 提案的规范可以通过下面的依赖添加到你的工程里

<dependency><groupId>jakarta.validation</groupId><artifactId>jakarta.validation-api</artifactId><version>2.0.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Spring Validation 也是 JSR 380 提案的一个实现方案。

随着 SpringBoot 版本的不断更新,有的版本自动加入了这两个 Spring Validation 的依赖包、不需要额外配置;而有的版本 需要手动 在项目的 pom.xml 里添加依赖。

所以不用纠结,如果项目缺少依赖(javax.validation.constraints 包找不到而报错)的时候,就自己手动加一下。

Validation 注解

JSR 380 定义了一些注解用于做数据校验,这些注解可以直接设置在 Bean 的属性上

  • @ NotNull

    不允许为 null 对象

  • @ AssertTrue

    是否为 true

  • @ Size

    约定字符串的长度

  • @ Min

    字符串的最小长度

  • @ Max

    字符串的最大长度

  • @ Email

    是否是邮箱格式

  • @ NotEmpty

    不允许为null或者为空,可以用于判断字符串、集合,比如 Map、数组、List

  • @ NotBlank

    不允许为 null 和 空格

我们用一个例子来举例用法

package com.bookstore.model;import javax.validation.constraints.*;public class User {@NotEmpty(message = "名称不能为 null")private String name;@Min(value = 18, message = "你的年龄必须大于等于18岁")@Max(value = 150, message = "你的年龄必须小于等于150岁")private int age;@NotEmpty(message = "邮箱必须输入")@Email(message = "邮箱不正确")private String email;// standard setters and getters 
}

大多数情况下,我们建议使用 NotEmpty 替代 NotNull、NotBlank

校验的注解是可以累加的,如上面的 @Min 和 @Max,系统会按顺序执行校验,任何一条校验触发就会抛出校验错误到上下文中

我们继续完善这个例子,步骤还是有点多的,需要仔细阅读哦

创建一个表单页

创建一个 user/addUser.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" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>添加用户</title>
</head><body><h2>添加用户</h2><form action="/user/save" method="POST"><div><label>用户名称:</label><input type="text" name="name"></div><div><label>年龄:</label><input type="text" name="age"></div><div><label>邮箱:</label><input type="text" name="email"></div><div><button type="submit">保存</button></div></form>
</body></html>
mapping

我们还需要在 control 类里设置 mapping

import javax.validation.Valid;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;
import com.bookstore.model.*;@Controller
public class UserControl {@GetMapping("/user/add.html")public String addUser() {return "user/addUser";}}

如果你启动工程后,现在应该可以输入 http://localhost:8080/user/add.html 打开页面了,我们运行一下看看

执行校验

前面 addUser.html 页面中的 form action 值配置的是 /user/save,所以我们增加一下这个请求的 Control 代码就可以执行校验了,在 Spring MVC当中执行校验非常简单

package com.bookstore.control;import com.bookstore.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;import javax.validation.Valid;@Controller
public class UserControl {@GetMapping("/user/add.html")public String addUser() {return "user/addUser";}@PostMapping("/user/save")public String saveUser(@Valid User user, BindingResult errors) {if (errors.hasErrors()) {// 如果校验不通过,返回用户编辑页面return "user/addUser";}// 校验通过,返回成功页面return "user/addUserSuccess";}}

大家仔细看 saveUser 这个方法的参数,在第一个参数 user 那,我们添加了参数注解 @Valid,然后我们新增了第二个参数 errors(它的类型是 BindingResult),顺序不要写错哦

  • @Valid 完整的包路径是 javax.validation.Valid;
  • BindingResult 类的路径是 org.springframework.validation.BindingResult

BindingResult 对象的 hasErrors 方法可以用于判断校验成功还是失败

  • 如果失败,回到添加用户的页面
  • 如果成功,显示成功页面
addUserSuccess.html

创建一个 user/addUserSuccess.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" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>添加用户</title>
</head><body><h2>添加用户成功</h2>
</body></html>

Spring Validation(二)

在上一节课中我们成功的运用了 Spring Validation 进行数据的验证,但是大家有没有发现一个问题,就是如果出错了,你并不知道错误的原因是什么

只要我们注册网站用户什么的,就经常看得见如下的效果

Thymeleaf 同样也支持这种能力,大家可以思考一下,如果想要实现这种能力,只要我们把错误结果返回到模板到中即可了。

这样的话,我们就有两个关键点要解决

  • 如何传递数据?
  • 如何显示具体的字段错误?

Control 改造

把数据传递到页面,这个我们在上一章节已经学过了,如果想显示具体字段的信息,就得需要结合模型来传输,比如 上一课的 User,现在我们改造一下 UserControl.addUser 方法

@GetMapping("/user/add.html")
public String addUser(Model model) {User user = new User();model.addAttribute("user",user);return "user/addUser";
}

如上面的代码,我们需要先创建一个 User 实例并传递到模板中去。

user/add.html 改造

在 user/add.html 模板当中,我们得去处理错误的状态,增加错误的样式和文案

th:object

为了让表单验证状态生效,你还需要在 form 标签里增加一个 th:object="${user}" 属性

th:object 用于替换对象,使用了这个就不需要每次都编写 user.xxx,可以直接操作 xxx 了

完整的如下

<form action="/user/save" th:object="${user}" method="POST">...
</form>
th:classappend

关于错误提示这个信息,我们还需要再分解一下。如果想显示错误的状态,我们就得定义一个错误的 css class ,比如

.error {color: red;
}

如果某个 html 标签拥有这个 error class 那么就会出现红色的字体,说明错误啦

如果设计师有明确错误的样式,可以跟随设计稿来开发,这里只是举例

现在我们就需要动态的管理表单的样式,如果有错误就再该标签上增加这个 class,否则不处理

你可以使用 th:classappend 这个语法,支持我们动态的管理样式

<div th:classappend="${#fields.hasErrors('name')} ? 'error' : ''">
</div>

如果错误信息里有 name 字段,上面的代码会生成

<div class="error">
</div>

注意 ${#fields.hasErrors('key')} 这个语法是专门为验证场景提供的,这里的 key 就是对象的属性名称,比如 User 对象的 name、age、email 等

th:errors

如果还想要显示出错误信息,你可以使用 th:errors="*{age}"属性,这个会自动取出错误信息来,完整的代码如下

<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

注意这里的 *由于我们在 form 中使用了 th:object="${user}",所以我们就可以通过 *{age}来获取具体的属性值

th:field

一般错误的时候,我们还希望显示上一次输入的内容,所以你可以使用 th:field 语法

<div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''"><label>年龄:</label><input type="text" th:field="*{age}" /><p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>
</div>

如果你想显示其他的字段,我们可以参考上面的代码

<div th:classappend="${#fields.hasErrors('name')} ? 'error' : ''"><label>用户名称:</label><input type="text" th:field="*{name}"><p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>
</div>

Thymeleaf 布局(Layout)

大多数的网站都有导航、底部等公共的东西,在一个网站里访问页面总是会显示相同的导航、底部之类的内容

这个时候,就需要借助布局(layout)知识了,注意我们这里说的布局并不是指的 css 布局哦,而是说网站的页面架构

layout 解决的是模板复用的问题,比如常见的网站是下面这样的

按照这个布局,我们可以把导航和底部做成布局组件,每个页面套用就可以了。

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

layout.html

现在我们继续完善我们的 bookstore ,创建一个 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>

如上的代码,这个layout页面中,我们添加了 header、container、footer 三个节点。重点在 container 这个节点上,我们使用了一个 th:include="::content" 语法

th:include="::content"

这个语法还是要理解一下,::content 指的是选择器,这个选择器指的就是加载当前页面的 th:fragment的值。

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

user/list.html

现在我们继续改造一下 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:replace="layout"

这里指定了布局的名称,这个一旦声明后,页面会被替换成 layout 的内容,记住不要指定布局名称错误哦,这个"layout"指的是 templates/layout.html

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

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

现在我们可以运行一下访问 /user/list.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"><h2>用户列表</h2><div><a href="/user/add.html">添加用户</a></div><table><thead><tr><th>用户名称</th><th>用户年龄</th><th>用户邮箱</th></tr></thead><tbody></tbody></table>
</div>
<footer class="footer"><div><p style="float: left">© youkeda.com 2017</p><p style="float: right">Powered by 优课达</p></div>
</footer></body>
</html>

仔细对比一下,确实 layout.html 和 user/list.html 页面合并渲染了

提示

表单的错误样式 .error 需要写在 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;}.error {color: red;}</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>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/477577.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大语言模型】ACL2024论文-20 SCIMON:面向新颖性的科学启示机器优化

【大语言模型】ACL2024论文-20 SCIMON&#xff1a;面向新颖性的科学启示机器优化 目录 文章目录 【大语言模型】ACL2024论文-20 SCIMON&#xff1a;面向新颖性的科学启示机器优化目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果推荐阅读指数&#xff1a;★★★★☆ …

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

docker镜像源配置、换源、dockerhub国内镜像最新可用加速源(仓库)

一、临时拉取方式 在docker pull后先拼接镜像源域名&#xff0c;后面拼接拉取的镜像名 $ docker pull dockerpull.org/continuumio/miniconda3 二、永久配置方式 vim修改/etc/docker/daemon.json&#xff0c;并重启docker服务。 # 创建目录 sudo mkdir -p /etc/docker# 写…

AMD(Xilinx) FPGA配置Flash大小选择

目录 1 FPGA配置Flash大小的决定因素2 为什么选择的Flash容量大小为最小保证能够完成整个FPGA的配置呢&#xff1f; 1 FPGA配置Flash大小的决定因素 在进行FPGA硬件设计时&#xff0c;选择合适的配置Flash是我们进行硬件设计必须考虑的&#xff0c;那么配置Flash大小的选择由什…

NVR录像机汇聚管理EasyNVR多品牌NVR管理工具/设备如何使用Docker运行?

在当今的安防监控领域&#xff0c;随着视频监控技术的不断发展和应用范围的扩大&#xff0c;如何高效、稳定地管理并分发视频流资源成为了行业内外关注的焦点。EasyNVR作为一款功能强大的多品牌NVR管理工具/设备&#xff0c;凭借其灵活的部署方式和卓越的性能&#xff0c;正在引…

【SKFramework框架】一、框架介绍

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享QQ群&#xff1a;398291828小红书小破站 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【Unity3D框架】SKFramework框架完全教程《全…

Python 版本的 2024详细代码

2048游戏的Python实现 概述&#xff1a; 2048是一款流行的单人益智游戏&#xff0c;玩家通过滑动数字瓷砖来合并相同的数字&#xff0c;目标是合成2048这个数字。本文将介绍如何使用Python和Pygame库实现2048游戏的基本功能&#xff0c;包括游戏逻辑、界面绘制和用户交互。 主…

排序算法(选择排序、直接插入排序、冒泡排序、二路归并排序)(C语言版)

对数组进行排序&#xff0c;主要演示选择排序、直接排序、冒泡排序、二路归并排序算法&#xff0c;附上代码演示 一、编写好各类排序方法的函数 &#xff08;1&#xff09; s_sort(int e[],int n):选择排序。 &#xff08;2&#xff09;si_sort(int e[],int n):直接插人排序。…

Unity图形学之Surface Shader结构

1.没有Pass&#xff1a;因为Render Path已经封装好了 Shader "Custom/Test" {Properties{_Color ("Color", Color) (1,1,1,1)_MainTex ("Albedo (RGB)", 2D) "white" {}_Glossiness ("Smoothness", Range(0,1)) 0.5_Meta…

数据集-目标检测系列- 牵牛花 检测数据集 morning_glory >> DataBall

数据集-目标检测系列- 牵牛花 检测数据集 morning DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 贵在坚持&#xff01; 数据样例项目地址&#xff1a; * 相关项目 1&#xff09;数据集可视化项目&#xff1a;git…

摄影:相机控色

摄影&#xff1a;相机控色 白平衡&#xff08;White Balance&#xff09;白平衡的作用&#xff1a; 白平衡的使用环境色温下相机色温下总结 白平衡偏移与包围白平衡包围 影调 白平衡&#xff08;White Balance&#xff09; 人眼看到的白色&#xff1a;会自动适应环境光线。 相…

Odoo :免费且开源的农牧行业ERP管理系统

文 / 开源智造Odoo亚太金牌服务 引言 提供农牧企业数字化、智能化、无人化产品服务及全产业链高度协同的一体化解决方案&#xff0c;提升企业智慧种养、成本领先、产业互联的核心竞争力。 行业典型痛点 一、成本管理粗放&#xff0c;效率低、管控弱 产品研发过程缺少体系化…

oracle查看锁阻塞-谁阻塞了谁

一 模拟锁阻塞 #阻塞1 一个会话正在往一个大表写入大量数据的时候&#xff0c;另一个会话加字段&#xff1a; #会话1 #会话2 会话2被阻塞了。 #阻塞2 模拟一个会话update一条记录&#xff0c;没提交。 另一个会话也update这一条记录&#xff1a; 会话2被阻塞了。 二 简单查…

HDR视频技术之三:色度学与颜色空间

HDR 技术的第二个理论基础是色度学。从前面的内容中可以了解到&#xff0c;光学以及人类视觉感知模型为人类提供了解释与分析人类感知亮度的理论基础&#xff0c;但是 HDR 技术不仅仅关注于提升图像与视频的亮度范围&#xff0c;同时也关注于提供更加丰富的色彩。因此&#xff…

神经网络中常用的激活函数(公式 + 函数图像)

激活函数是人工神经网络中的一个关键组件&#xff0c;负责引入非线性&#xff0c;从而使神经网络能够学习和表示复杂的非线性关系。没有激活函数&#xff0c;神经网络中的所有计算都是线性变换&#xff0c;而线性模型的表达能力有限&#xff0c;无法处理复杂的任务。 激活函数…

Redis——Raft算法

Raft使用较为广泛的强一致性、去中心化、高可用的分布式协议&#xff0c;即使在网络、节点故障等情况下&#xff0c;多个节点依然能达到一致性。 其中redis、etcd等都用到了这种算法 在Redis集群中&#xff0c;采取的主从复制结构&#xff0c;当主节点宕机后&#xff0c;哨兵会…

C 语言复习总结记录二

C 语言复习总结记录二 一 控制语句 1、语句的分类 表达式语句函数调用语句复合语句控制语句空语句 控制语句 控制程序的执行流程&#xff0c;实现程序的各种结构方式 C 语言支持三种结构 &#xff1a;顺序结构、选择结构、循环结构&#xff0c;由特定的语句定义符组成C语言…

网络无人值守批量装机-cobbler

网络无人值守批量装机-cobbler 一、cobbler简介 ​ 上一节中的pxe+kickstart已经可以解决网络批量装机的问题了,但是环境配置过于复杂,而且仅针对某一个版本的操作系统进批量安装则无法满足目前复杂环境的部署需求。 ​ 本小节所讲的cobbler则是基于pxe+kickstart技术的二…

基于深度学习CNN算法的花卉分类识别系统01--带数据集-pyqt5UI界面-全套源码

文章目录 基于深度学习算法的花卉分类识别系统一、项目摘要二、项目运行效果三、项目文件介绍四、项目环境配置1、项目环境库2、环境配置视频教程 五、项目系统架构六、项目构建流程1、数据集2、算法网络Mobilenet3、网络模型训练4、训练好的模型预测5、UI界面设计-pyqt56、项目…

HarmonyOs鸿蒙开发实战(20)=>一文学会基础使用组件导航Navigation

敲黑板&#xff0c;以下是重点技巧。文章末尾有实战项目效果截图及代码截图可参考 1.概要 Navigation是路由导航的根视图容器Navigation组件主要包含​导航页&#xff08;NavBar&#xff09;和子页&#xff08;NavDestination&#xff09;&#xff0c;导航页不存在页面栈中&am…