Thymeleaf简介
入门freemarker与Thymeleaf的对比配置 运行原理
展示数据
小案例----->> th 操作属性 标准表达式
条件表达式数据访问方式内置对象可选参数 Thymeleaf遍历信息
运算符
算数运算符关系运算符逻辑运算符条件运算符条件运算符 链接网址模板模板
定义和引用片段 模板引擎对比 Thymeleaf简介
Thymeleaf是一个现代的服务器端 Java 模板引擎,适用于 Web 和独立环境。
Thymeleaf 的主要目标是为您的开发工作流程带来优雅的自然模板— HTML 可以在浏览器中正确显示,也可以作为静态原型工作,从而在开发团队中实现更强的协作。
Thymeleaf 具有 Spring framework 模块、大量与您最喜爱的工具集成,以及插入您自己的功能的能力,是现代 HTML5 JVM Web 开发的理想选择 — 尽管它可以做的还有很多。
更多描述请转官网
---------------------摘自官网描述--------------------
入门就是先整一个Thymeleaf程序
参照freemarker的整合,这里将freemarker的启动器换成Thymeleaf
依赖的变动就这么一点;
这里选择最新版本的依赖,如果出现一些其他问题,后面再去解决;
freemarker与Thymeleaf的对比 配置详细官网配置请见spring官网
源码是这样写的:
public class GTVGApplication { ... private final TemplateEngine templateEngine; ... public GTVGApplication(final ServletContext servletContext) { super(); ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); // HTML is the default mode, but we set it anyway for better understanding of code templateResolver.setTemplateMode(TemplateMode.HTML); // This will convert "home" to "/WEB-INF/templates/home.html" templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); // Template cache TTL=1h、If not set, entries would be cached until expelled templateResolver.setCacheTTLMs(Long.valueOf(3600000L)); // Cache is set to true by default、Set to false if you want templates to // be automatically updated when modified. templateResolver.setCacheable(true); this.templateEngine = new TemplateEngine(); this.templateEngine.setTemplateResolver(templateResolver); ... }}
以上默认配置表示 thymeleaf使用UTF-8编码,
访问时前缀template,后缀为html,
即直接转发或重定向到这个文件即可;
直接访问这个文件不可以么?
还真不可以,这个跟freemarker一致,
运行原理Thymeleaf在Spring Boot项目中放入到resources/templates中。这个文件夹中的内容是无法通过浏览器URL直接访问的(和WEB-INF效果一样),所有Thymeleaf页面必须先走控制器。
模板解析器
//模板解析器是实现来自 Thymeleaf API 的接口的对象,全限定名称为:org.thymeleaf.templateresolver.ITemplateResolverpublic interface ITemplateResolver { ... public TemplateResolution resolveTemplate( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map
为了处理数据,并适应国际化的需求,模板引擎在获得request,response,还需要获得请求来自的地区;
public class HomeController implements IGTVGController { public void process( final HttpServletRequest request, final HttpServletResponse response, final ServletContext servletContext, final ITemplateEngine templateEngine) throws Exception { WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); templateEngine.process("home", ctx, response.getWriter()); }}
展示数据准备好上下文对象后,现在我们可以告诉模板引擎使用上下文处理模板(按其名称),并将其传递给响应编写器,以便可以将响应写入它;
友情提醒----在展示数据之前还需要引入一个命名空间
因为我们在表单中使用的这些非标准属性是 HTML5 规范所不允许的。
1,Thymeleaf中表达式必须依赖标签而不能单独使用
2,标准变量表达式一般在开始标签中,以 th开头
3,语法为:
4,表达式中可以通过${}取出域中的值并放入标签的指定位置
5,${}在这里不能单独使用,必须在 th:后面的双引号里使用
比如后端---->>
@Controllerpublic class ShowController { @RequestMapping("/goodsinfo") public String showInfo(Map
前端:
"${goodsname}"
html文件中th并没有把规定标签,所以要引入th空间;
注:这里时idea自带的提示,官方建议是引入
xmlns:th="http://www.thymeleaf.org,
**data-th-语法是在 HTML5 中编写自定义属性的标准方法,该语法无需开发人员使用任何命名空间名称;
html源代码
@Controllerpublic class ShowController { @RequestMapping("/goodsinfo") public String showInfo(Map
后端传过来的数据
"${goodsname}"
简单总结一下---->
Thymeleaf将后端的数据 用th:*来展现,且只能在前标签中才能生效;
基本书写格式----->>th:标签中属性名="${}"
如果为了更方便区分可以在属性前加 data-th-标签中属性名="$()"
这两种形式是完全等效的,自己习惯哪一种就用哪一种;
以上内容均为白话形式,如有不当之处,还请参阅
Thymeleaf官方文档
namespace-------th可以操作的属性
只要是html能识别的标签中的属性基本就可以;
有一点不太友好的是----1999/xhtml的引入提示会没有提示;
案例—>>
前端传来一个Goods对象
@RequestMapping("/one") public ModelAndView showone(Map
菜单信息
id:goodsName:price:goodsDesc:
当返回的信息属性带有null时,thymeleaf并不会报错而freemarker会报错;
那如果返回对象为null时----->>
报异常
所以----->>
菜单信息
判断是否为空id:goodsName:price:goodsDesc:
th:text
文本文本只是在单引号之间指定的字符串。它们可以包含任何字符,但您应该使用 对其中的任何单引号进行转义。’
Now you are looking at a template file、
home.welcome=Welcome to our fantastic grocery store! Welcome to our grocery store! Welcome to our grocery store!
前端显示结果
当我们用了text时,会替换事先做好的内容;
数字文本
数字文字就是:数字。
The year is 1492. In two years, it will be 1494.
布尔文本
true false
...
在这个例子中,写在大括号外面,所以是ThymeLeaf照顾它。如果它写在大括号内,那将是OGNL / SpringEL引擎来负责处理,达到的效果是一样的
true 与 false 只能在if中使用;
空文本
还可以使用文本:null
文本标记
类似于html中的选择器
数字,布尔和空文本实际上是文本标记的特殊情况。
这些标记允许在标准表达式中进行一些简化。它们的工作方式与文本文本 () 完全相同,但它们只允许使用字母 ( 和 )、数字 ()、方括号 ( 和 )、点 ()、连字符 () 和下划线 ()。所以没有空格,没有逗号等
它不需要围绕它们的任何引号。因此,我们可以这样做:
...
hello
附加文本
文本,无论是文本还是计算变量或消息表达式的结果,都可以使用运算符轻松追加:+
文字替换
文本替换允许轻松格式化包含变量值的字符串
这些替换必须用竖线 () 包围,例如:|
这相当于:
例子
文本替换可以与其他类型的表达式结合使用:
在文本替换中只允许使用变量/消息表达式
设置属性值
此模板开始时是y一个静态原型,而不是 Web 应用程序的模板,如果要实现动态交互,则需要给此表单添加action,
除了输入属性,更改设置它的标签的属性值要用到----th:attr
但是,如果我们想一次设置多个属性呢?XML 规则不允许您在标记中设置属性两次,因此将采用逗号分隔的赋值列表,
给定所需的消息文件,这将输出:
但是实际上我们会用到类似于 th:text th:title等类似简单的方式来实现,除非在thymeleaf中没有,那么才应该考虑用这种方式;
在thyme中的标签属性---->>>为特定属性设置值
预置和附加属性
thymeleaf
这种预置和附加标签之间的区别---->>
thymeleaf1 thymeleaf4
thymeleaf2
thymeleaf3
classappend用于向元素添加 CSS 类或样式片段,而不会覆盖现有属性;
attrappend用于添加属性,并不会覆盖哦原来的属性而是在后面追加
styleappend用于追css样式
这些属性会将其评估结果附加(后缀)或前缀(前缀)到现有属性值。
固定值布尔属性
HTML具有布尔属性的概念,这些属性没有值,并且一个属性的优先性意味着值是"真的"。在 XHTML 中,这些属性只取 1 个值,即它本身。
例如:checked
标准方言包含允许您通过评估条件来设置这些属性的属性,因此,如果计算为 **true,**则该属性将设置为其固定值,如果计算为 false,则不会设置该属性:
肥肠鱼蒸熊掌
Simple expressions:Variable expressions: ${...}Selection Variable expressions: *{...}Message expressions: #{...}link URL expressions: @{...}Fragment expressions: ~{...}LiteralsText literals: , ,…'one text''Another one!'Number literals: , , , ,…0343.012.3Boolean literals: , truefalseNull literal: nullLiteral tokens: , , ,…onesometextmainText operations:String concatenation: +Literal substitutions: |The name is ${name}|Arithmetic operations:Binary operators: , , , , +-*/%Minus sign (unary operator): -Boolean operations:Binary operators: , andorBoolean negation (unary operator): , !notComparisons and equality:Comparators: , , , (, , , ><>=<=gtltgele)Equality operators: , (, ==!=eqne)Conditional operators:If-then: (if) ? (then)If-then-else: (if) ? (then) : (else)Default: (value) ?: (defaultvalue)Special tokens:No-Operation: _
简单表达式:变量表达式:${...}选择变量表达式:*{...}消息表达式:#{...}链接网址表达式:@{...}片段表达式:~{...}文字文本文本:,,...'one text''Another one!'数字文字: , , , ,...0343.012.3布尔文字: ,truefalse空文本:null文字标记: , , ,...onesometextmain文本操作:字符串串联:+文字替换:|The name is ${name}|算术运算:二元运算符: , , , , ,+-*/%减号(一元运算符):-布尔运算:二元运算符: ,andor布尔否定(一元运算符):,!not比较和平等:比较器: , , , ( , , ,><>=<=gtltgele)等运算符: , (,==!=eqne)条件运算符:如果-那么:(if) ? (then)如果-然后-否则:(if) ? (then) : (else)违约:(value) ?: (defaultvalue)特殊代币:无操作:_所有这些功能都可以组合和嵌套:
条件表达式Onions 2.41 10? ${'九折'} : #{''}">yes 评论--爆发虎 comment/s view
后端—>
@ResponseBody @RequestMapping("/comments") public String comments(Integer GoodsId){ Goods goodsById = goodsService.findGoodsById(GoodsId); return goodsById.getGoodsname(); }
th:if 不仅会计算布尔条件。它的功能不至于此,它将按照以下规则计算指定的表达式—>>
If value is not null:
If value is a boolean and is .true
If value is a number and is non-zero
If value is a character and is non-zero
If value is a String and is not “false”, “off” or “no”
If value is not a boolean, a number, a character or a String.
(If value is null, th:if will evaluate to false).
反向属性th:unless,我们可以在前面的示例中使用该属性,而不是在OGNL表达式中使用:th:if th:unlessnot
view
th:switch
还有一种方法可以使用Java中的开关结构的等效物有条件地显示内容:/属性集。th:switch 与th:case
User is an administrator User is a manager
请注意,只要将一个属性计算为 ,同一开关上下文中的所有其他属性的计算结果为 。th:case true th:case false
默认选项指定为 :th:case="*"
User is an administrator User is a manager User is some other thing
案例演示:
Onions 2.41 10? ${'九折'} : #{''}">yes 招牌菜 如果菜名是酸菜鱼则显示招牌菜 评论--爆发虎 comment/s view
数据访问方式 第一种----->>
${person.father.name}
第二种----->>
${person[‘father’][‘name’]}
第三种,如果是map映射
${person[father’].name}
还可以用
${personsArray[0].name}
还可以调用方法
${person.createCompleteName()}
${person.createCompleteNameWithSeparator(’-’)}
#ctx: the context object、 上下文对象#vars: the context variables、上下文变量#locale: the context locale、 位置#request: (only in Web Contexts) the object.HttpServletRequest请求对象#response: (only in Web Contexts) the object.HttpServletResponse响应对象#session: (only in Web Contexts) the object.HttpSession session对象#servletContext: (only in Web Contexts) the object.ServletContext ServletContext对象
这样就可以向jsp中那样,将信息存储在与域对象中然后在取出来,如果可以的话还可以在Thymeleaf中做一些数据处理
除了这些基本对象之外,Thymeleaf还将为我们提供一组实用程序对象,这些对象将帮助我们在表达式中执行常见任务。
#execInfo: information about the template being processed.#messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris: methods for escaping parts of URLs/URIs#conversions: methods for executing the configured conversion service (if any).#dates: methods for java.util.Date objects: formatting, component extraction, etc.#calendars: analogous to #dates, but for java.util.Calendar objects.#numbers: methods for formatting numeric objects.#strings: methods for String objects: contains, startsWith, prepending/appending, etc.#objects: methods for objects in general.#bools: methods for boolean evaluation.#arrays: methods for arrays.#lists: methods for lists.#sets: methods for sets.#maps: methods for maps.#aggregates: methods for creating aggregates on arrays or collections.#ids: methods for dealing with id attributes that might be repeated
一些常用的内置对象案例
1表示整数位至少保持一位(如果不够则补0),COMMA表示, point表示 、
部分结果----->>
request:
session:
application:
可选参数
Not only can variable expressions be written as ${...}, but also as *{...}.
两种方式----1,${}2,*{}星号语法作用 于所选对象上的表达式,而不是整个上下文上的表达式。如果没有选定的对象,${}和*{}的操作完全相同。
例如----->>官方给出的案例
Name: Sebastian. Surname: Pepper. Nationality: Saturn.
上述等同于
Name: Sebastian. Surname: Pepper. Nationality: Saturn.
实际运用---->>
id: Sebastian goodsname: Pepper. price: Saturn. price: Saturn. goodsdesc: Saturn. goodsdesc: Saturn. goodsdesc: Saturn.
实际上如果没有定义th:object那么*{}与${}达到的效果是一样的;
Thymeleaf遍历信息list
菜品编号 菜品名称 菜品价格 菜品描述 >
直接取Map:
很多时候我们不存JavaBean而是将一些值放入Map中,再将Map存在Model中,我们就需要对Map取值,对于Map取值你可以${Map名['key']}来进行取值。也可以通过 M a p 名 . k e y 取 值 , 当 然 你 也 可 以 使 用 ‘ {Map名.key}取值,当然你也可以使用` Map名.key取值,当然你也可以使用‘{map.get(‘key’)}`(java语法)来取值,完整代码如下:
Map
place: feeling:
遍历Map:
如果说你想遍历Map获取它的key和value那也是可以的,这里就要使用和List相似的遍历方法,使用th:each="item:${Map名}"进行遍历,在下面只需使用item.key和item.value即可获得值。完整代码如下:
Map遍历
案例—>>
@RequestMapping("/showA") public ModelAndView showA(Map
键值 菜品编号 菜品名称 菜品价格 菜品描述 优惠信息 操作 删除
小结---->>
示例中u为迭代遍历。
th:each="element,status:${listinfo}" 其中status表示迭代状态。
1,index:当前迭代器的索引 从0开始
2,count:当前迭代对象的计数 从1开始
3,size:被迭代对象的长度
4,even/odd:布尔值,当前循环是否是偶数/奇数 从0开始
5,first:布尔值,当前循环的是否是第一条,如果是返回true否则返回false
6,last:布尔值,当前循环的是否是最后一条,如果是则返回true否则返回false
索引 序号 总数 偶数索引 基数索引 第一? 最后? 菜品编号 菜品名称 菜品价格 菜品描述 优惠信息
放上官方给出的案例—>>
Return to home Product list
NAME PRICE IN STOCK Onions 2.41 yes
可以迭代的对象,
任何实现的对象java.util.Iterable任何实现 的对象。java.util.Enumeration任何实现的对象,其值将在迭代器返回时使用,而无需在内存中缓存所有值。任何实现java.util.Iterator的对象。迭代映射时,迭代变量将属于 类 。java.util.Mapjava.util.Map.Entry任何数组。任何其他对象都将被视为包含对象本身的单值列表。 运算符 算数运算符
算数运算符
某些算术运算也可用±*/%
请注意,这些运算符也可以应用于 OGNL 变量表达式本身(在这种情况下,将由 OGNL 而不是 Thymeleaf 标准表达式引擎执行):
请注意,其中一些运算符存在文本别名:div 即/ mod 即%
关系运算符关系运算符 略
XML 确定不应在属性值中使用 和 符号,因此应将它们替换为相应的符号
> 1">更简单的替代方法可能是使用其中一些运算符存在的文本别名
逻辑运算符**逻辑运算符****逻辑运算符** 2 0 2 1 1
在最新版的thymeleaf中对&& 与 ||不太 友好,报错了
注意:
像这种空的时候,字符串默认为’’,如果是其他数据则是null
条件表达式旨在仅计算两个表达式中的一个,具体取决于计算条件的结果(条件本身是另一个表达式)。
让我们看一个示例片段(引入另一个属性修饰符, ):th:class
...
后端
map.put("ifU",true);
hello
条件表达式也可以使用括号嵌套:
...
Else 表达式也可以省略,在这种情况下,如果条件为 false,则返回 null 值:
...
默认值—>>>
在freemarker中也有默认值的表达方式---->>>>
${!''}
而在thymeleaf中,默认表达式是一种不带 then 部分的特殊条件值。它等效于某些语言(如Groovy)中存在的Elvis运算符,允许您指定两个表达式:如果计算结果不为null,则使用第一个表达式,但如果计算结果为null,则使用第二个表达式。
例如---->>
... Age: 27.
如您所见,运算符是 ,我们在这里使用它来指定名称的默认值(在本例中为文本值),仅当计算结果为 null 时。因此,这等效于:?{age}
Age: 27.
与条件值一样,它们可以在括号之间包含嵌套表达式:
Name: Sebastian
链接网址
URL是Web应用程序模板中的一等公民,Thymeleaf标准方言为它们提供了特殊的语法,即语法:@@{…}
有不同类型的网址:
绝对网址---->>>
绝对网址:http://www.thymeleaf.org
相对网址---->>
页面相对:user/login.html
或者是上下文相对:
(将自动添加服务器中的上下文名称)/itemdetails?id=3
相对于服务器:(允许在同一服务器中调用另一个上下文(= 应用程序)中的 URL。~/billing/processInvoice
import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.ModelAndView;@Controllerpublic class testController { @RequestMapping("/test/{name}") @ResponseBody public String test01( @PathVariable String name) { return name; }}
传入多个参数
如果需要多个参数,这些参数将用逗号分隔:@{/order/process(execId=${execId},execType='FAST')}URL 路径中也允许使用变量模板:@{/order/{orderId}/details(orderId=${orderId})}
@Controllerpublic class testController { @RequestMapping("/test/{name}/{age}") @ResponseBody public String test01(@PathVariable String name, @PathVariable Integer age) { return name + age; }}//点我呀
这些表达式的实际处理及其到将要输出的 URL 的转换由注册到正在使用的对象中的接口的实现完成。org.thymeleaf.linkbuilder.IlinkBuilderITemplateEngine
默认情况下,此接口的单个实现是类的注册,这对于脱机(非Web)和基于Servlet API的Web方案都足够了。其他方案(如与非 ServletAPI Web 框架的集成)可能需要链接生成器界面的特定实现。org.thymeleaf.linkbuilder.StandardlinkBuilder
除了上述方式还可以通过请求发或者响应重定向来实现另一个业务需求;
比如删除商品信息
controller层
@RequestMapping("/showall") public ModelAndView showall(Map
索引 序号 总数 偶数索引 基数索引 第一? 最后? 菜品编号 菜品名称 菜品价格 菜品描述 优惠信息 操作 删除
如果是请求转发,则地址栏上会发生变化
引入css
引入Javascript:
小案例—>>
<?xml version="1.0" encoding="UTF-8"?>
后端:----->>>
@RequestMapping("/showA") public ModelAndView showA(Map
这里实现了弹窗提醒,主要是运用了th:click方式来触发js函数;
超链接:
超链接
th:onclick
给元素绑定事件,单击事件并传递参数
写法1:仅仅支持数字和布尔类型参数的传递,字符串不支持
删除
写法2:支持数字和文本类型的参数传递
删除
模板模板的标准存放位置/templates/footer.html
© 2022 The Good Thymes Virtual Grocery
模板 定义和引用片段 在我们的模板中,我们经常希望包含其他模板中的部分,如页脚,页眉,菜单等部分…
我们想要在某个模板中添加一些别的模板中的或者其资源中的信息,我们会用到
th:insert th:replace th:include
案例—我们要在页面底部添加一些公司以及认证的商标信息,如果在每一个页面中添加会比较繁琐,于是可以单独建一个页面来实现对该页面元素的引用
定义片段 :(页面名称footer)
© 2022 The Good Thymes Virtual Grocery
th:insert插入
那么我们只需要在需要的页面中引入以下内容就可以达到效果
等效代码---->>>
页面效果
在这里有一个关键的----->>th:fragment
© 2011 The Good Thymes Virtual Grocery
th:fragment 就像一个选择器,
首先定义一个模板
th:fragment="selector"
Happy New Year
当在别的页面引用的时候格式---->>
"~{templatename::selector}"或者"(templatename::selector)"
th:insert ="~{被引用的页面名即模板名::片段名}",
当然更简便的是
th:insert="(被引用的页面名::片段名)"
~{::selector}"或插入来自同一模板的片段,匹配 。如果在显示表达式的模板上找不到,则模板调用(插入)堆栈将遍历到最初处理的模板(根),直到在某个级别匹配。"~{this::selector}"selectorselector这种片段方法的一大优点是,可以将片段写入浏览器完全可显示的页面中,具有完整甚至有效的标记结构,同时仍然保留使Thymeleaf将它们包含到其他模板中的能力。
即使不定义 fragment片段,我们也可以完成插入/引用
例如:
footer.html
© 2011 The Good Thymes Virtual Grocery
这个就跟元素选择器很像了,通过其属性引用它,类似于CSS选择器:id
除此之外还有----->> th:replace th:include,
th:replace 替换
© 2022 The Good Thymes Virtual Grocery1 © 2022 The Good Thymes Virtual Grocery2
th:include只插入内容
ThymeLeaf2
三种的对比
th:insert是最简单的:它只会插入指定的片段作为其主机标记的主体。
th:replace实际上将其主机标记替换为指定的片段。
th:include与 类似,但不是插入片段,而是仅插入此片段的内容。
但是在thymeleaf3.0及以后的版本中不在建议这么食用这三种标签,所以了解即可;
刚刚测试每修改一次就需要重启一些服务,比较繁琐,有一种方式是引入spring开发工具来实现修改之后的自动编译
引入依赖
设置一下idea
设置registry
某些资源在更改时不一定需要触发重新启动。 例如,Thymeleaf 模板可以就地编辑。 默认情况下,更改资源 /meta-INF/maven, /meta-INF/resources, /resources, /static, /public, 或者 /templates不会触发重新启动,但会触发 实时重新加载 。 如果要自定义这些排除项,可以使用 spring.devtools.restart.exclude财产。 例如,仅排除 /static和 /public您将设置以下属性:
特性
yaml
spring.devtools.restart.exclude=static/,public/
如果要保留这些默认值并 添加 其他排除项,请使用 spring.devtools.restart.additional-exclude而是财产。
片段的参数化
为模板片段创建更像函数的机制,定义的片段可以指定一组参数:th:fragment
别的资源可以引用该资源下的片段内容:
ThymeLeaf1ThymeLeaf2ThymeLeaf2ThymeLeaf2
现在已经不推荐这种用法了;所以了解一下即可;
模板引擎对比jsp优点:1、功能强大,可以写java代码2、支持jsp标签(jsp tag)3、支持表达式语言(el)4、官方标准,用户群广,丰富的第三方jsp标签库缺点:性能问题。不支持前后端分离freemarkerFreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出。FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP。它不仅可以用作表现层的实现技术,而且还可以用于生成XML,JSP或Java 等。目前企业中:主要用Freemarker做静态页面或是页面展示优点:1、不能编写java代码,可以实现严格的mvc分离2、性能非常不错3、对jsp标签支持良好4、内置大量常用功能,使用非常方便5、宏定义(类似jsp标签)非常方便6、使用表达式语言缺点:1、不是官方标准2、用户群体和第三方标签库没有jsp多ThymeleafThymeleaf是个XML/XHTML/HTML5模板引擎,可以用于Web与非Web应用。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。Thymeleaf的可扩展性也非常棒。你可以使用它定义自己的模板属性集合,这样就可以计算自定义表达式并使用自定义逻辑。这意味着Thymeleaf还可以作为模板引擎框架。优点:静态html嵌入标签属性,浏览器可以直接打开模板文件,便于前后端联调。springboot官方推荐方案。缺点:模板必须符合xml规范VUE: 前后端分离,最多,未来趋势