Thymeleaf3.0简介
Posted 沧海一滴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thymeleaf3.0简介相关的知识,希望对你有一定的参考价值。
http://www.thymeleaf.org/doc/articles/layouts.html
thymeleaf的初次使用(带参请求以及调用带参js方法)
之前对于前端框架接触较少,第一次接触thymeleaf,虽说看起来并不复杂但我还是花费了好一会儿才弄懂。
话不多少下面就简单说一下我在项目中的应用。
首先是java代码 controller层 将需要在前端展示的信息放入model中:
@RequestMapping("getAll") public String getAll(Model model){ List<ScheduleJob> list = scheduleJobService.getAllJob(); model.addAttribute("list", list); return "sch/quartz/list"; }
之后便是在前端html页面的遍历
1 <tr th:each="job:${list}">
2 <td th:text="${job.name }">任务名</td>
3 <td th:text="${job.group }">任务组</td>
4 <td th:text="${job.status }">状态</td>
5 <td th:text=\'${job.cronExpression }\'></td>
6 <td th:text="${job.className }">类名</td>
7 <td th:text="${job.description }">描述</td>
8 <td><a href="#" th:href="@{/quartz/pause(name=${job.name},group=${job.group})}">暂停</a></td>
9 <td><a href="#" th:href="@{/quartz/resume(name=${job.name},group=${job.group})}">恢复</a></td>
10 <td><a href="#" th:href="@{/quartz/run(name=${job.name},group=${job.group})}">执行一次</a></td>
11 <td><a href="#" th:href="@{/quartz/delete(name=${job.name},group=${job.group})}">删除</a></td>
12 <td><button id="edit" th:name="${job.name}" th:id="${job.group}" onclick="test(this)">修改cron表达式</button></td>
13 </tr>
这里直接在<tr>标签中 用 th:each 放入需要遍历的内容,以及定义变量名;在<td>标签中用th:text来展示内容。
在<a>标签中用 普通的href不能实现带参的接口请求,所以需要使用 th:href 的这种语法来实现带参的接口请求,参数用()跟在后面就可以。
至于调用js的带参方法就需要用12行那种办法 将参数作为 th 标签的name或者id传入方法中,具体的js方法如下;
1 function test(obj){
2 var cron = $("#cron").val();
3 var name = $(obj).attr("name");
4 var group = $(obj).attr("id");
5 $.post("edit",{"name":name,"group":group,"cron":cron},function(){
6 alert("更新成功!!!");
7 window.location.reload();
8 });
9 }
这样就能实现js带参方法的调用,目前来说我所知道的可以实现的就是这种方式。
补充一点,前端遍历的实体类需要重写toString方法,并且必须是如下格式的:
1 @Override 2 public String toString() { 3 StringBuilder builder = new StringBuilder(); 4 builder.append("{name:\\""); 5 builder.append(name); 6 builder.append("\\", group:\\""); 7 builder.append(group); 8 builder.append("\\", cronExpression:\\""); 9 builder.append(cronExpression); 10 builder.append("\\", status:\\""); 11 builder.append(status); 12 builder.append("\\", description:\\""); 13 builder.append(description); 14 builder.append("\\", className:\\""); 15 builder.append(className); 16 builder.append("\\"}"); 17 return builder.toString(); 18 }
这样才能在页面上成功遍历。
对于thymeleaf,目前我也就掌握了这些简单的使用。
http://www.cnblogs.com/stan-tiger/p/5622824.html
spring security + thymeleaf 判断登录用户的权限
spring security的UserDetailService是我自己定义的。
@Component public class MyUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) { // Get user and his authority from userRepository
user = userRepository.getUserByUsername(username);
Set<GrantedAuthority> authorities = new HashSet<>(); user.getPermissions().forEach(r -> authorities.add(new SimpleGrantedAuthority(r.getValue())));
MyUser myUser = new MyUser (username, ....); return myUser; }}
MyUser.java,扒的User.java
public class MyUser implements UserDetails, CredentialsContainer {...}
index.html:
<script th:inline="javascript"> /*<![CDATA[*/ var username = /*[[${name}]]*/ \'\'; var isAnonymous = [[${#authorization.expression(\'hasRole(\'\'ROLE_ANONYMOUS\'\')\')}]]; var isSomeAuth = [[${#authorization.expression(\'hasRole(\'\'SOME_AUTH\'\')\')}]]; var s = [[${#authorization.expression(\'hasRole(\'\'ROLE_SOME_AUTH\'\')\')}]];
从数据库获取的用户Permission,在前端怎么都拿不到,返回的结果一直false,但是通过${authentication.principal}打印的结果却能够看到。
debug发现expression方法会调用org.springframework.security.access.expression.SecurityExpressionRoot的方法hasAnyRole,这个方法会将hasRole()里的字符串加上ROLE_与UserDetails的GrantedAuthority逐个比较,因此,最简单的方法就是把数据库中权限用ROLE_开头
http://blog.csdn.net/kakadiablo/article/details/59108583
Thymeleaf简介
什么是Thymeleaf
Thymeleaf是网站或者独立应用程序的新式的服务端java模板引擎,可以执行HTML,XML,JavaScript,CSS甚至纯文本模板。
Thymeleaf的主要目标是提供一个以优雅的高可维护型的方式创建模板,为了达到这个目的,他建立了自然模板的概念,以一种不影响模板设计原型的方式将逻辑注入到模板文件中,可以显著的减少设计和开发之间的沟通成本。
在需要的情况下,Thymeleaf可以创建一个各种校验模式的模板(尤其是基于HTML5)
什么样的模板可以使用Thymeleaf处理
Thymeleaf 允许使用六种模板,每个被称为一种模板模式:
- HTML
- XML
- TEXT
- JAVASCRIPT
- CSS
- RAW
这其中有两种标记语言模板模式(HTML,XML),三种文本语言模板模式(TEXT,JAVASCRIPT,CSS),和一种无操作模板模式(RAW)
其中:
HTML模板模式可以使用任何HTML输入,包括HTML5,HTML4或XHTML,全验证或无验证都将可以被执行,模板中的代码和结构将被尽最大可能性的解析输出。
XML模板模式将被用来执行XML的输入,在这种模式下,如果被要求严格验证,则比如没有闭合标签,属性没有引号等,都将输出异常,注意一点是,无验证(通过DTD或XMLSchema)仍然是可以执行的。
文本模板模式将可以使用一种非标记性的特殊语法。它可能是一个电子邮件的模板或者文本模板等模板文件。注意:HTML模式或XML模式也可以使用文本模板模式处理,但在这种情况下,他们不会被解析为标记语言,每个标记,DOCTYPE,comment等都将视为单纯的文本。
JavaScript模板模式可以使用Thymeleaf处理Js文件。这意味着它能够在JS文件中以HTML文件同样的方式使用数据模型。但它还整合了一些JS的专有属性,如一些特定编码和脚本语言。JavaScript模板是一种支持特殊语法的文本模板模式。
CSS模板模式和JavaScript模板模式类似,即可以使用Thymeleaf处理CSS文件,它也是用支持特殊语法的文本模板模式。
RAW模板模式是一种非执行模板,他用了处理一些保持原样的资源(如文件,某Url的响应等),比如,在一些格式之外,可能会有一些信息需要原封不动的展现出来,这些应该明确的告知Thymeleaf不要讲它们进行执行。
方言:标准方言
Thymeleaf 是一种可扩展的模板引擎(其实应该称呼为模板引擎框架),它允许您自由的定义制作一个可以精确到任何程度的模板。
一个对象,运用一些逻辑(如标签,文本,注释,或仅仅是一个占位符),被称为处理器,这些处理器加一些额外的东西,被称为方言。其中Thymeleaf的核心库提供了一个一目了然的方言被称为标准方言,他应该能够满足大多数用户的需求。
应明确一点就是,方言实际上可以是没有处理器,完全有其他的类型的东西注册,但处理器是一种最常见的使用方式。
本教程即使用标准方言,你可以在下面页中了解这个方言的每一个属性和语法功能的定义,即使他没有明确提及。
当然,如果用户想要定义自己的处理逻辑,和使用库中的各种先进功能,那么,可以定义自己的方言(甚至扩展标准方言),而模板引擎可以一次配置多种方言。
官方thymeleaf-spring3和thymeleaf-spring4集成包都定义了另一种方言被称为“String标准方言”,大多数相当于标准方言,但有一小部分为了适应Spring框架的某些功能(如利用Spring的EL表达式而不是Thymeleaf标准的OGNL)所以,如果你是一个Spring mvc用户,请你不要在浪费时间抓紧学习,因为那你在这里学习的所有东西,都讲在你的Spring项目中使用
Thymeleaf标准方言可以在任何模式的模板中使用,尤其适合面向web的模板模式(如HTML5和XHTML),除了HTML5,他可以支持和验证一下的XHTML格式:XHTML 1.0 Transitional, XHTML 1.0 Strict, XHTML 1.0, 和 XHTML 1.1.
标准方言的大多数处理器都是属性处理器。他们在浏览器处理HTML模板文件之前,他会简单处理额外的属性,如,在jsp中使用标签库可以包括代码段,但代码段浏览器并不会直接显示一样:
<form:inputText name="userName" value="${user.name}" />
Thymeleaf中,可以实现同样的功能:
<input type="text" name="userName" value="niufennan" th:value="${user.name}" />
这个会在浏览器中被正确的显示,同时,也容许我们指定一个默认值(可选)("niufennan"),当静态文件在浏览器中打开的时候,显示的是Thymeleaf使用模板中${user.name}的值对value中的值进行了替换的结果。
这将可以使设计人员和开发人员使用几乎相同的模板文件,并且可以使用极少的工作,就使一个静态文件转换为开发模板文件。有这样能力的模板通常称之为自然模板
古泰虚拟商店
当前及未来示例代码下载地点
商店站点
为了更好的说明Thymeleaf的各种概念,教程所使用的Demo可以从项目网站下载。
这个项目是一个假想的购物网站,将提供足够的场景来演示Thymeleaf的各种不同的特点。
这个购物网站需要一个非常简单的实体模型,产品(Products)销售给客户(Customers),同时创建订单(Orders),并且,还需要我们管理用户对产品的评论(Coomments)
一个简单的服务层:
public class ProductService {
...
public List<Product> findAll() {
return ProductRepository.getInstance().findAll();
}
public Product findById(Integer id) {
return ProductRepository.getInstance().findById(id);
}
}
最后,web层将有一个过滤器,用来根据请求的url来执行Thymeleaf
private boolean process(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
try {
//静态资源不使用框架
if(request.getRequestURI().startsWith("/css")||
request.getRequestURI().startsWith("/images")||
request.getRequestURI().startsWith("/favicon")
)
{
return false;
}
/*
* 根据controller/url的映射规则,获取将要执行的控制权的request
* 如果没有控制权可用,返回false,让其他的filter或者servlet来执行
*/
IGTVGController controller = application.getTemplateEngine().resolveControllerForRequest(request);
if (controller == null) {
return false;
}
/*
* 获取 TemplateEngine 实例.
*/
ITemplateEngine templateEngine = application.getTemplateEngine();
/*
* 写 response headers
*/
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
/*
* 执行控制权并处理模板
* 将结果写入response
*/
controller.process(
request, response, this.servletContext, templateEngine);
return true;
} catch (Exception e) {
throw new ServletException(e);
}
}
还有IGTVGController 接口
public interface IGTVGController {
public void process(
HttpServletRequest request, HttpServletResponse response,
ServletContext servletContext, ITemplateEngine templateEngine) throws Exception;
}
现在要做的就是IGTVGController接口的实现,主要实现了从服务中检索数据和处理模板使用的ITemplateEngine 对象
最后,他的效果图如下
但是,首先让我们看看模板引擎是如何初始化的。
创建并且配置一个模板引擎
在过滤器process方法中加入如下的代码:
ITemplateEngine templateEngine = application.getTemplateEngine();
这意味着GTVGApplication类中在加载的时候创建和配置了Thymeleaf启动应用的最重要对象:模板引擎实例(ITemplateEngine接口的实现)
我们的org.thymeleaf.TemplateEngine对象的初始化就像是这样:
public class GTVGApplication {
...
private static TemplateEngine templateEngine;
...
public GTVGApplication(ServletContext servletContext) {
ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver(servletContext);
//有String类型重载
templateResolver.setTemplateMode(TemplateMode.HTML);
// 这段将把 "home" 转换为 "/WEB-INF/templates/home.html"
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
//模板的缓存的生存时间默认为1小时,如果没有设置,将被缓存到LRU(缓存淘汰算法)中
templateResolver.setCacheTTLMs(3600000L);
//缓存默认为true,若希望修改模板后自动更改(如调试时),可设置为false
templateResolver.setCacheable(true);
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
}
...
}
当然,配置TemplateEngine对象的方式有很多,但这几行代码将让我们学会足够的必要步骤。
模板解释器
从模板解释器开始:
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
模板解释器对象从如下接口实现:
org.thymeleaf.templateresolver.ITemplateResolver:
public interface ITemplateResolver {
...
/*
* 模板通过名字或内容来解析。星期如果他有主模板的情况下还将尝试将此解析为另一个模板的片段
* 如果这个模板不能通过解析,将返回null.
*/
public TemplateResolution resolveTemplate(
final IEngineConfiguration configuration,
final String ownerTemplate, final String template,
final Map<String, Object> templateResolutionAttributes);
}
有这些解释器对象决定我们的模板在GTVG应用中将被如何访问,并在org.thymeleaf.templateresolver.ServletContextTemplateResolver中实现,我们将在Servlet的上下文中检索制定的模板文件资源。Servlet上下文指的是javax.servlet.ServletContext对象,他广泛的在每一个java web应用中存在。并将使用web应用程序的根作为资源文件的根路径。
但这不能说所有的模板都如此解析,因为我们可以给他设置一些配置参数。首先,模板模式中,其中一个标准为:
templateResolver.setTemplateMode("HTML");
HTML是ServletContextTemplateResolver的默认模板模式,
但出于代码可读性还是显示设置。
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
顾名思义,这是模板路径的前缀和后缀,通过模板的名称,可以将真实的路径传递到引擎来使用它。
使用这个配置:模板名称"product/list"的路径将为:
servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html")
通过配置为cachtTTLMS属性配置一个总的时间量,可以配置模板解释器在缓存中存活的时间:
templateResolver.setCacheTTLMs(3600000L);
当然,如果缓存达到了最大缓存,并且此模板是最早的缓存条目,那么在达到TTL之前,他还是有可能被清理出缓存的。
缓存的行为和大小,都可以由用户自己定义,既可以实现ICacheManager接口自己实现规则,也可以通过修改StandardCacheManager对象设置缓存默认管理规则。
我们将在以后学习模板解释器,现在让我们来看看模板引擎对象。
模板引擎
模板引擎对象是接口org.thymeleaf.ITemplateENgine的实现,Themeleaf的core包提供一个默认的实现:org.thymeleaf.TemplateEngine,下边是创建引擎的一个例子:
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
很简单,是吧,我们所需要的仅仅是创建一个实例并设置一个模板解释器。
一个模板解释器是一个TemplateEngine必须的参数,当然,还需要其他的参数,将在后边介绍(如消息解释器,缓存大小等),现在,这些已经满足我们需要了。
我们的模板引擎现在已经可以启动,并可以使用Thymeleaf创建我们的页面了。
使用文本
多语言欢迎页
我们的第一个任务是为我们的商店网站创建一个首页。
第一个版本,将是一个非常简单的页面:仅仅有个title和一个欢迎信息。在/WEB-INF/template/home.html文件:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body>
<p th:text="#{home.welcome}">欢迎您光临本店!</p>
</body>
</html>
首先你会注意到,这个文件是HTML5标准的DOCTYPE格式,他可以在主流浏览器中正确的显示(浏览器会忽略掉一下他不认识的属性,如th:text)。
但是,你也可能注意到,这个模板并不是一个完全有效的HTML5文档,因为有HTML5规格中没有的非标准属性,如th:*属性,事实上,我们添加了一个xmlns:th属性在html标签,用来分类非html5的标记或属性。
<html xmlns:th="http://www.thymeleaf.org">
那么,如何使用一个完全符合HTML5标准的模板呢?也很容易,使用Thymeleaf数据属性语法即可,它使用data-前缀的方式来使用:
<!DOCTYPE html>
<html>
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body>
<p data-th-text="#{home.welcome}">欢迎您光临本店!</p>
</body>
</html>
自定义的data-前缀属性是html5完全支持的语法,这样,上面这段代码就是一个完全符合HTML5定义的文档
这两种语法是完全等价的,可以无缝替换,但为了使代码更为简洁,本教程使用命名空间符号(th:),另外,th:语法是更常用的语法,在每一种Thymeleaf模板模式都是可以使用,但data-这种方式只能在HTML模式中使用。
使用th:text和外部文本
外部文本通常是模板文件中需要替换的代码,通常保存在外部的独立文件(通常是特定的.properties文件)中。他们可以很容易的设置其他语言的等效文本(这一过程通常称为国际化)。这些外在的文本片段通常被称为"messages"
Messages需要一个唯一性的Key进行标识,Thymeleaf允许你通过指定一个#{...}语法格式的Message对应一个具体的文本。如:
<p th:text="#{home.welcome}">欢迎您光临本店!</p>
我们可以看到Thymeleaf标准方言的两个主要的语言点:
-
th:text属性,他声明设置表达式的值,并使表达式返回的值来填充标签内容,替换或设置标签内部的内容,当前例子中即替换“欢迎光临本店”这些字。
-
#{home.welcome}表达式,一个标准的表达式语法,指出在模板中,th:text属性说对应Message的key,即使用home.welcome对应的value替换现有内容。
现在,对应的外部文本在哪?
在Thymeleaf中这些文件的位置都是可配的,他通过实现org.thymeleaf.messageresolver.IMessageResolver接口进行配置,通常会实现一个基于.properties文件的实现,当然也同样可以根据实际情况进行自定义实现,如将Message存储在数据库中。
如果我们在模板引擎初始化的时候没有配置任何消息解释器的实现,那就意味着我们使用的是默认的一个消息解析实现,实现类为:org.thymeleaf.messageresolver.StandardMessageResolver.
这个消息解释器会扫描/WEB-INF/template/home.html相同目录下的相同名称的.properties文件,比如:
- /WEB-INF/templates/home_en.properties 英文
- /WEB-INF/templates/home_fr.properties 法语
- /WEB-INF/templates/home_jp.properties 日语
- /WEB-INF/templates/home.properties 默认
其中的一个properties文件如下(en):
home.welcome=Welcome to our grocery store!
我们已经完成了我们所需要的Thymeleaf框架的模板,接下来创建Home的Controller。
上下文
为了处理模板,我们将创建一个实现了之前看到的IGTVTController接口的HomeController类:
public class HomeController implements IGTVGController {
@Override
public void process(HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext,
TemplateEngine templateEngine) throws Exception {
// TODO Auto-generated method stub
WebContext ctx=new WebContext(request,response,servletContext,
request.getLocale());
templateEngine.process("home", ctx,response.getWriter());
}
}
可以看到,第一件事就是创建了一个context,一个实现了org.thymeleaf.context.IContext接口的Thymeleaf上下文。上下文应该包含了模板引擎所执行所有所需数据的变量的map,并且引用了Locale所需的外部文件。
public interface IContext {
public Set<String> getVariableNames();
public Locale getLocale();
public boolean containsVariable(final String name);
public Object getVariable(final String name);
}
并且扩展了一个专用接口,org.thymeleaf.context.IWebContext,用于基于ServletApi的应用使用,如SpringMVC:
public interface IWebContext extends IContext {
public HttpServletRequest getRequest();
public HttpServletResponse getResponse();
public HttpSession getSession();
public ServletContext getServletContext();
}
Thymeleaf核心库为每个接口给出了一个实现:
- org.thymeleaf.context.Context implements IContext
- org.thymeleaf.context.WebContext implements IWebContext
正如在controller的代码中看到的那样,我们使用了WebContext,事实上我们也必须使用它,因为这是ServletContextTemplateResolver必须使用一个IWebContext的实现类。
WebContext ctx = new WebContext(request, response,servletContext, request.getLocale());
这个构造函数的四个参数中有三个是必须的,第四个参数系统可以设置一个默认的本地区域,如果没有设置,则使用系统默认。
从WebContext还提供了一些专用的表达式,可以在模板中获得request的参数,request,session,application的属性等,比如:
- ${x}:返回一个Thymeleaf的context中存储的变量或request的属性。
- ${param.x} 返回request的参数x(可以为复合值)
- ${session.x} 返回session的属性x
- ${application.x} 返回一个servlet上下文的属性x
就在执行前,一个特殊的变量被设置为包含了Context和WebContext的全context对象(即所有实现了IContext的对象),被称为执行信息(execInfo),这个变量有两个您可以从模板中使用的数据。
- 模板名:#{execInfo.templateName},一个引擎执行时的特定名称,对应正在执行的模板。
- 当前的日期时间:#{execInfo.now},一个Calender对象,对应引擎开始执行的时刻值。
执行模板引擎
随着context对象已经准备好,剩下只需要指定模板名称和context,以执行模板引擎,兵传递给response的writer,以写入响应。
templateEngine.process("home", ctx, response.getWriter());
看看执行结果(图):
更多的文本和变量
非转义文本
一个最简版的Home页面似乎已经准备好了,但是,如果有这样的需求怎么办呢:
home.welcome=欢迎光临这个<b>超级棒</b>的商店!
如果是这样的,输出结果为:
home.welcome=欢迎光临这个<b>超级棒 </b>的商店!
很显热,这并不是我希望看见的,我们希望b作为一个标签来处理,而不是转义后显示在浏览器上。
这是浏览器对th:text的默认行为。如果我们希望Thymeleaf直接使用html标签,而不是转义他们,我们可以使用不同的属性th:utext("unescaped text")
<p th:utext="#{home.welcome}">欢迎您光临本店!</p>
这样输出信息就是所需要的了:
<p>欢迎光临这个<b>超级棒</b>的商店!</p>
thymeleaf 回显富文本编辑器的内容
把 th:text 换成 th:utext 即可
th:text属性
可对表达式或变量求值,并将结果显示在其被包含的 html 标签体内替换原有html文本
文本连接:用“+”符号,若是变量表达式也可以用“|”符号
e.g.
若home.welcome=Welcome to our <b>fantastic</b> grocery store!
用<p th:text="#{home.welcome}"></p>解析结果为:
<p>Welcome to our <b>fantastic</b> grocery store!</p>
2 th:utext属性
解决方案
<p th:utext="#{home.welcome}"></p>即可。
Welcome to our fantastic grocery store!
等效于html :<p>Welcome to our <b>fantastic</b> grocery store!</p>
使用和显示变量
如果我想在首页多现实一下信息,比如显示日期,就像下边这样:
欢迎这个超级棒的网店
当前日期为:2016-9-2
那么首先,我们应该回到首页,添加当前时间到context变量:
WebContext ctx=new WebContext(request,response,servletContext,
request.getLocale());
//时间
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
ctx.setVariable("today",sdf.format(cal.getTime()));
templateEngine.process("home", ctx,response.getWriter());
我们已经添加了一个String变量在context中,现在修改模板:
<p th:utext="#{home.welcome}">欢迎您光临本店!</p>
<p>当前日期为: <span th:text="${today}">2016年9月5日</span></p>
可以看到,我们仍然使用th:text属性,但是语法稍有不同,这次使用的是${...}而不是#{...},这是一个用来显示变量值的表达式。它使用OGNL语言来映射context中的变量。
\\({today}表达式只是简单的表示为:获取一个叫today的变量,但是这个表达式也可以变得很复杂,如\\){user.name}的意思是获取一个user变量,并调用他的getName()方法。
属性值有相当多的可能性,如:消息,变量表达式等等,下一章将展示这些都是什么.
标准表达式语法
在将这个小demo发展成一个真正的网店之前,先休息一下,熟悉一下Thymeleaf标准方言中最重要的组成部分:Thymeleaf标准表达式语法。
在之前,已经看到过两种有效的属性表达式:消息和变量表达式:
<p th:utext="#{home.welcome}">欢迎您光临本店!</p>
<p>当前日期为: <span th:text="${today}">2016年9月5日</span></p>
但还有更多的类型和有趣的细节,我们都还不知道,下面我们首先来一个标准表达式的快速总结:
- 简单表达式
- 变量表达式:${...}
- 选定变量表达式:*{...}
- 信息表达式:#{...}
- 链接表达式:@{...}
- 片段表达式:~{...}
- 字面值
- 文本值:\'one text\',\'Another one!\',...
- 数值:1,34,3.0,12.3...
- 布尔值:true,false
- Null值:null
- 标记符号值(token):one,sometext,main,...(?)
- 操作符
- 字符串连接:+
- 文本替换:|The name is ${name}|
- 算数运算符:
- 基本操作符:+,-,*,/,%
- 取负值(一元运算符):-
- boolean运算符:
- 基本二元操作符:and,or
- 一元操作符:!,not
- 比较和判断:
- 比较运算符:>,<,>=,<=,(gt,le,ge,le)
- 判断相等运算符:==,!=(eq,ne)
- 条件运算符
- if-then:(if)?(then)
- if-then-else:(if)?(then):(else)
- default:(value)?:(defaultvalue)
- 特殊标记
- 无操作:_
所有这些还可以合并嵌套使用:
\'User is of type \'+(${user.isAdmin()}?\'Administrator\':(${user.type}?:\'Unknown\'))
信息
我们已经知道,消息表达式许可我们将:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
连接到资源,将内容转换为:
home.welcome=欢迎您光临本店!
但是,如果文本内容不是完全静态的怎么办?例如,我们想实现在已知某位用访问本站的时候(登录后),在页面显示他的名字:
<p>张三,您好!欢迎您光临本店!</p>
这意味着我们需要一个参数:
home.welcome={0},您好!欢迎您光临本店!
参数是根据java.text.MessageFormat标准语法指定,意味着你可以添加数字,日期等api指定的格式。
为了给参数赋值,可以给一个session的属性为用户:
<p th:utext="#{home.welcome(${session.user.name})}">
张三,您好!欢迎您光临本店!
</p>
如果需要,可以指定多个参数,用逗号分隔,事实上,消息的key本身也可以是一个变量:
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
张三,您好!欢迎您光临本店!
</p>
变量
我们已经知道${...}表达式实际上是OGNL表达式在执行context中映射的变量。
更多关于OGNL语法和功能的详细信息,可以阅读http://commons.apache.org/ognl/
在SpringMVC的应用中,OGNL将被SpringEL替代,但两种语法非常相似,常用的语法完全相同。
通过OGNL定义,我们可以指定
<p>当前日期为:: <span th:text="${today}">2016-8-13</span>.</p>
事实上,是这样实现的:
ctx.getVariables("today");
OGNL也允许我们创建更强大的表达式,比如说这样:
<p th:utext="#{home.welcome(${session.user.name})}">
张三,您好!欢迎您光临本店!
</p>
实际上他是这样执行的:
((User)ctx.getVariables("session").get("user")).getName();
通过getter方法导航是OGNL语法的一大特点:下面演示更多用法:
//访问属性使用(.)进行访问,相当于调用getter方法
${person.father.name}
//访问属性也可以通过方括号[]进行访问,属性名作为一个变量通过单引号(\')或双引号(")写在方括号中
${person[\'father\'][\'name\']}
//如果对象是一个map,可以用引号和方括号的语法将相当于直营一个调用它的key获取对象的get方法。
${countriesByCode.ES}
${personsByName[\'张三\'].age}
//对于数组或集合的索引也用方括号来执行
${personsArray[0].name}
//可以调用各种方法,无论是否有参数
${person.createCompleteName()}
${person.createCompleteNameWithSeparator(\'-\')}
基本对象表达式
当使用OGNL表达式的context变量的时候,可以使用更方便的表达方式。这些对象也是用来#符号:
- #ctx: context对象
- #vars:context属性
- #locale:context本地化信息
- #request:(仅限WebContext) HttpServletRequest对象
- #response:(仅限WebContext) HttpServletResponse对象
- #session:(仅限WebContext)HttpSession对象
- #servletContext:(仅限WebContext)ServletContext对象
所以我们可以这样使用
这里是:<span th:text="${#locale.country}">中国</span>.
你可以在附录1中查看这些对象的全部参考
工具对象表达式
除了基本对象,Thymeleaf还为我们提供了一套实用对象,可以帮助我们在执行表达式中解决一下常见的任务:
- #execInfo:正在处理的模板信息
- #messages:获取外部信息的内部变量的一个实用方法,同时也可以用#{...}获取
- #uris:针对URL或URI进行一些转码的方法
- #conversions:根据配置执行一些转换方法
- #dates:针对java.util.Date对象的实用方法:包括日期格式化,日期提取等。
- #calendars:与#dates相似,但针对的是java.util.Calendar对象。
- #numbers:针对numeric对象格式化的实用方法
- #strings:针对String对象的实用方法,包括包含,判断起始,前/后追加等方法
- #objects:针对object类的一般实用方法
- #boolean:针对boolean运算的一些实用方法
- #arrays:针对数组的实用方法
- #lists:针对list的实用方法
- #sets:针对set的实用方法
- #map:针对map的实用方法
- #aggregates:在数组或集合中创建聚合的一些实用方法
-
#ids:用于处理可能重复的标识属性的使用方法,例如,作为迭代的变量。
你可以在附录2中查看这些工具对象的全部参考
在首页中使用格式化日期
现在,我们知道了这些工具对象表达式,就可以改变首页中显示日期的方式,首先,改变我们HomeController中的代码,将:
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
ctx.setVariable("today",sdf.format(cal.getTime()));
这三行可以合并为1行
ctx.setVariable("today",Calendar.getInstance());
然后,修改模板文件的相应行为:
<p>
当前日期为: <span th:text="${#calendars.format(today,\'yyyy-MM-dd\')}">2016年9月5日</span>
</p>
选定变量表达式(或使用{...}) ###
变量表达式不但可以使用${...}表达式,同时也可以使用{...}表达式。
他们有个最重要的区别:*{...}表达式的值是在选定的对象而不是整个context的map。也就是说,如果没有选定的对象,*{...}和${...}没有区别
那么问题来了:什么是选定对象?一个th:object对象属性,使用用户权限页面来演示一下:
<div th:object="${session.user}">
<p>姓名:<span th:text="*{firstName}">张三</span></p>
<p>年龄:<span th:text="*{age}">26</span></p>
<p>国籍:<span th:text="*{nationlity}">中国</span></p>
</div>
这相当于:
<div>
<p>姓名:<span th:text="${session.user.firstName}">张三</span></p>
<p>年龄:<span th:text="${session.user.age}">26</span></p>
<p>国籍:<span th:text="${session.user.nationlity}">中国</span></p>
</div>
当然,也可以混合使用
<div th:object="${session.user}">
<p>姓名:<span th:text="*{firstName}">张三</span></p>
<p>年龄:<span th:text="${session.user.age}">26</span></p>
<p>国籍:<span th:text="*{nationlity}">中国</span></p>
</div>
当一个使用选定对象的地方,选定的对象其实就是使用了#object表达式的${...}表达式
<div th:object="${session.user}">
<p>姓名:<span th:text="${#object.firstName}">张三</span></p>
<p>年龄:<span th:text="${session.user.age}">26</span></p>
<p>国籍:<span th:text="${#object.nationlity}">中国</span></p>
</div>
也就是说,如果没有已经完成的选定对象,那么,*{...}和${...}两种表达式是完全等价的。
<div>
<p>姓名:<span th:text="*{session.user.firstName}">张三</span></p>
<p>年龄:<span th:text="*{session.user.age}">26</span></p>
<p>国籍:<span th:text="*{session.user.nationlity}">中国</span></p>
</div>
Url链接
由于其重要性,URL是web应用程序模板的一等公民,Thymeleaf的标准方言都为它定义了特殊语法:@{...}
他有不同类型的网址:
- 绝对地址,比如:https://niufennan.github.io/
- 相对地址,可以有如下方式:
- 相对于页的,比如:user/login.html
- 相对于上下午的,比如:/itemdetails?d=3(将自动添加服务器的上下文名称)
- 相对于服务器的,比如:~/billing/processInvoice(可以在同一服务器中的不同上下文(即application)中使用)
- 相对于协议的,比如://cdn.bootcss.com/jquery/2.2.3/jquery.min.js
真正将这些表达式转换并输出为URL的工具,是一个注册在ITemplateEngine中的一个org.thymeleaf.linkbuilder.ILinkBuilder接口的实现类。
Thymeleaf可以在任何情况下处理绝对地址,但相应的,也会要求你给予一个实现了IWebContext接口的context对象,他包含了一些创建链接需要的来自Http请求的相关信息。
在默认情况下,注册的实现是类org.thymeleaf.linkbuilder.StandardLinkBuilder,它对于基于Servlet API的网络或离线应用已经足够了,而其他情况(如非ServletAPI的web项目)则可能需要自己创建接口的实现。
举例说明一下,需使用th:href属性:
<!-- 输出: \'http://localhost:8080/gtvg/order/details?orderId=3\' -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- 输出: \'/gtvg/order/details?orderId=3\' -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- 输出: \'/gtvg/order/3/details\' -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
注意:
- th:href是一个修饰属性,一旦设置这个属性,它会计算链接,并设置<a\\>标签内的href属性的url值。
- 可以对url的参数使用表达式(比如orderId=${o.id}),url上所需的编码工作,也会自动执行。
- 如果需要多个参数,用逗号(,)分开即可如:(@{/order/process(execId=${execId},execType=\'FAST\')})
- 网络路径中也可以使用变量模板,如:@{/order/{orderId}/details(orderId=${orderId})}
- 相对URL使用/开始,如/order/details,将自动适应上下文的名词前缀。
- 如果不清楚cookie是否被启用,一个jsessionid=...的后缀可能被加入到url中,用于回话保存,这就是所谓的URL重写。Thymeleaf允许利用Servlet的api为每一个url,使用response.encoding来扩展重写过滤器。
- th:href允许有一个静态工作的url配置在模板中,这样,我们的模板即使在不工作时,仍然可以通过浏览器互相连接。
就像#{...}一样,URL表达式中的值也可以是另一个表达式的结果:
<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{\'/details/\'+${user.login}(orderId=${o.id})}">view</a>
为首页制作一个简单菜单
现在已经知道了如何创建一个连接,是时候给首页增加一个小菜单了,以告诉大家这个站点还有些什么内容。
<p>请选择:</p>
<ol>
<li><a href="product/list.html" th:href="@{/product/list}">产品列表</a></li>
<li><a href="order/list.html" th:href="@{/order/list}">订单列表</a></li>
<li><a href="subscribe.html" th:href="@{/subscribe}">订阅新闻</a></li>
<li><a href="userprofile.html" th:href="@{/userprofile}">用户权限</a></li>
</ol>
相对于服务器根的URL
还有一个附加的语法,可用于指向一个服务器的根地址(而不是上下午的根地址),以便于指向同一服务器中的不同上下文。他的语法为:@{~/path/to/something}
片段
片段表达式是用一种简单的方式来标识一个片段的标记,并可以将他移动的其余模板,它允许我们执行重写模板,传递给其他模板参数等操作。
做常用的片段使用方式是插入使用,属性值为th:insert或th:replace(更多内容见后边部分)
<div th:insert="~{commons :: main}">...</div>
它可以在任何地方使用,就像其他变量一样:
<div th:with="frag=~{footer :: #main/text()}">
<p th:insert="${frag}">
</div>
本教程稍后的部分有一篇完整的关于模板布局的介绍,其中包含变量表达式更深层次的介绍。
字面值
文本值
字面值指的是包含在单引号之间字符,它可以使任何字符,但应该尽量避免使用\\\'
现在你看到的是模板文件.
数值
顾名思义,数值显示的就是一个数字:
<p>今年是<span th:text="2016">1942</span>.</p>
<p>两年后,将是<span th:text="2013 + 2">1944</span>.</p>
布尔值
布尔值只有true和false两个值,举例:
<div th:if="${user.isAdmin()} == false"> ...
注意,在这个例子中,==false是写在了\\({...}的外边,所以使Thymeleaf本身在支持它,如果写在了\\){...}的里边,则变为由OGNL或SpringEL库来支持它。
<div th:if="${user.isAdmin() == false}"> ...
null值
null值可以这样使用
<div th:if="${variable.something} == null"> ...
标记符号值(token)
实际上,数值,布尔值,null值都是一种特殊的token值。
这些token值允许为标准表达式进行一点点的简化,他们的工作方式和文本值完全一样,但是只能使用字符(a-z,A-Z),数值(0-9),括号即[ ]和( ),
所以不能有空格,括号等。
并且,他可以不被包含在单引号中:
<div th:class="content">...</div>
而不是这样(这样为文本值):
<div th:class="\'content\'">...</div>
追加文本
一段文本,无论是一段字面值,变量的返回值,还是信息表达式,都可以使用+来追加一段文本
<div th:text="\'这个人的名字为: \' + ${user.name}">
字面值替换
还可以直接替换一段字面值中包含的字符串信息,而无需通过操作符+
这些替换必须被竖线(|)包围,如:
<span th:text="|欢迎光临这个应用, ${user.name}!|">
即相当于:
<span th:text="\'欢迎光临这个应用, \' + ${user.name} + \'!\'">
格式替换也可以和其他表达式相结合使用:
<span th:text="${onevar} + \' \' + |${twovar}, ${threevar}|">
注意:只有变量表达式${...}可以出现在|...|中,其他的如布尔,数值或文字的token,条件表达式等都不可以使用。
算数运算
在表达式中还可以使用一些算数运算,如:+,-,*,/,%
<div th:with="isEven=(${prodStat.count} % 2 == 0)" >
注意:同布尔值的例子一样,这个同样可以写成使用OGNL或SpringEL支持的方式:
<div th:with="isEven=${prodStat.count % 2 == 0}">
注意:有两个运算符存在别名:div(/),mod(%)
比较和判断
表达式中的值可以通过>,<,>=,<=进行比较,也可以通过==和!=操作符进行相等的判断。注意在xml中使用的时候,不应该直接使用<,>,应当使用<
和>来代替
<div th:if="${prodStat.count} > 1">
<div th:text="\'执行模式为 \' + ( (${execMode} == \'dev\')? \'Development\' : \'Production\')">
注意,这些都存在着别名:gt(>),lt(<),ge(>=),le(<=),not(!),eq(==),neq/ne(!=)
条件表达式
条件表达式为根据条件(另一个表达式)来选择两个表达式中的一个。
看一个示例:
<tr th:class="${row.even}? \'even\' : \'odd\'">
...
</tr>
条件表达式的所有的三个部分均在一个自表达式中,意味着他可以用在变量${...},*{...}
,信息#{...}
,URL@{...}
或字面量(\'...\')中.
他还可以使用()来进行嵌套
<tr th:class="${row.even}? (${row.first}? \'first\' : \'even\') : \'odd\'">
...
</tr>
else部分可以省略,如果条件为否,则为一个null值
<tr th:class="${row.even}? \'alt\'">
...
</tr>
默认表达式(Elvis运算符)
默认表达式是一种特殊的没有then部分的条件表达式。在一些语言,如Groovy中,它相当于Elvis运算符,它允许有两个表达式,第二个只有在第一个返回null的时候才执行。
修改一下用户权限页,如:
<div th:object="${session.user}">
...
<p>年龄: <span th:text="*{age}?: \'(没有输入)\'">27</span>.</p>
</div>
正如看到的那样,使用?:操作符,我们在指定当年龄无效的时候,给定一个默认值(使用字面值),这条代码相当于:
<p>年龄: <span th:text="*{age != null}? *{age} : \'(没有输入)\'">27</span>.</p>
和条件值一样,他也可以使用括号来实现嵌套:
<p>
姓名:
<span th:text="*{name}?: (*{admin}? \'Admin\' : #{default.username})">张三</span>
</p>
无操作标记
无操作标记是使用下划线表示(_)
这背后是想指定一个标记来表达期望的结果是什么也不做,也就是说,如果处理的属性(如th:text)没有值。
这将允许开发人员使用原型文本作为默认值,如:
<span th:text="${user.name} ?: \'no user authenticated\'">...</span>
将可以改为:
<span th:text="${user.name} ?: _">no user authenticated</span>
直接使用原型,可以使使代码更加灵活
日期的转换与格式化
Thymeleaf可以使用双大括号的方式,为变量${...}和选择*{...}表达式通过配置转换服务进行数据转换。
它基本上是这样的:
<td th:text="${{user.createTime}}">...</td>
这个双大括号${{}}通知Thymeleaf对user.createTime表达式的结果进行转换服务,并在输出结果之前进行格式化操作。
转换服务器默认使用IStandardConversionService的实现类(StandardConversionService类),它只能够简单的执行一个对象的toString(),即将一个对象转为字符串服务,有关注册一个转换服务的更多信息,可以查看稍后更多配置部分。
在thymeleaf-spring3和thymeleaf-spring4有一整套基于Spring转换服务的Thymeelaf转换服务配置,因此他可以自动实现${{}}和*{{}}的服务。
预处理
Thymeleaf表达式除了这些特征,还为我们提供了预处理的功能。
预处理是指在正常的完成一个表达式之前执行的工作。他允许对最终执行的实际表达式进行修改.
一个预处理表达式和一个正常表达式几乎一样,但他在双下划线之间(__$(表达式)__)
比如,国际化的配置文件message_jp.properties的一个条目包含了一个OGNL表达式用于调用一个静态方法:
article.text=@myapp.translator.Translator@translateToJapaese({0})
以及另一个等效的Message_en.properties:
article.text=@myapp.translator.Translator@translateToEnglish({0})
对此,可以先创建一个用于标记的表达式,这个表达式的值取决于区域设置,为此,使用预处理,然后让Thymeleaf去执行它:
<p th:text="${__#{article.text(\'textVar\')}__}">一些文字...</p>
例如日本,上边预处理与如下内容等效:
<p th:text="${@myapp.translator.Translator@translateToJapaese(textVar)}">Some text here...</p>
预处理中如有字符串__可以使用\\_\\_来进行转义。
设置属性值
本章将介绍如何设置或修改标签属性的值,下一章将讲解如何设置内容的值。
属性值通用设置方式
我们的网站会不定期发布一些新闻,我们希望我们的用户能够订阅他,所以我们创建一个/WEB-INF/templates/subscribe.html的表单模板:
<form action="subscribe.html">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="订阅" />
</fieldset>
</form>
这个看起来不错,但是,这个更像是一个普通的html页面,首先,这个表单的action是指向了模板文件本身,并没有重写URL,第二,提交按钮的值,他是一个普通的文本,而我们希望他是一个国际化的。
使用th:attr属性,它具有设置或修改一个标签属性值的能力。
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="订阅!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
用法很简单:th:attr将是一个值对应一个属性的表达式,在转换处理后,将会返回如下结果:
<form action="/gtvg/subscribe">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="subscribe me!"/>
</fieldset>
</form>
除了更新了属性值,还可以看到,应用的已经自动将url更新为context前缀的url。
如果,我们想在同时更新多个属性呢?xml的规则不允许在一个标签内设置两个同名的属性,所以,可以用逗号来分割th:attr的值,比如:
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
将转换为:
<img src="/gtgv/images/gtvglogo.png" title="这里是logo" alt="这里是logo" />
特定属性值设定的方式
到了现在,你可能会发现,像:
<input type="submit" value="订阅" th:attr="value=#{subscribe.submit}"/>
这种,看起来是一个非常难看的模板。这种指定属性值的赋值方式很明显不是最优雅的一种创建方式模板。
事实也的确如此,这也就是为什么th:attr很少出现在实际应用的模板中,实际中经常使用的是th:*来实现标签中特定的属性。
在Thymeleaf的标准方言中,通常都是很直观的方式,如设置一个按钮的value值,使用th:value表达式,如:
<input type="submit" value="订阅" th:value="#{subscribe.submit}"/>
看起来感觉好多了,然后在看一下表单中的action属性:
<form action="subscribe.html" th:action="@{/subscribe}">
在之前home.html模板中的th:href,其实就在使用的这个语法:
<li><a href="product/list.html" th:href="@{/product/list}">产品列表</a></li>
Thymeleaf针对每一个属性都有一个特定的设置语法:
th:abbr | th:accept | th:accept-charset | th:accesskey |
th:action | th:align | th:alt | th:archive |
th:audio | th:autocomplete | th:axis | th:background |
th:bgcolor | th:border | th:cellpadding | th:cellspacing |
th:challenge | th:charset | th:cite | th:class |
th:classid | th:codebase | th:codetype | th:cols |
th:colspan | th:compact | th:content | th:contenteditable |
th:contextmenu | th:data | th:datetime | th:dir |
th:draggable | th:dropzone | th:enctype | th:for |
th:form | th:formaction | th:formenctype | th:formmethod |
th:formtarget | th:fragment | th:frame | th:frameborder |
th:headers | th:height | th:high | th:href |
th:hreflang | th:hspace | th:http-equiv | th:icon |
th:id | th:inline | th:keytype | th:kind |
th:label | th:lang | th:list | th:longdesc |
th:low | th:manifest | th:marginheight | th:marginwidth |
th:max | th:maxlength | th:media | th:method |
th:min | th:name | th:onabort | th:onafterprint |
th:onbeforeprint | th:onbeforeunload | th:onblur | th:oncanplay |
th:oncanplaythrough | th:onchange | th:onclick | th:oncontextmenu |
th:ondblclick | th:ondrag | th:ondragend | th:ondragenter |
th:ondragleave | th:ondragover | th:ondragstart | th:ondrop |
th:ondurationchange | th:onemptied | th:onended | th:onerror |
th:onfocus | th:onformchange | th:onforminput | th:onhashchange |
th:oninput | th:oninvalid | th:onkeydown | th:onkeypress |
th:onkeyup | th:onload | th:onloadeddata | th:onloadedmetadata |
th:onloadstart | th:onmessage | th:onmousedown | th:onmousemove |
th:onmouseout | th:onmouseover | th:onmouseup | th:onmousewheel |
th:onoffline | th:ononline | th:onpause | th:onplay |
th:onplaying | th:onpopstate | th:onprogress | th:onratechange |
th:onreadystatechange | th:onredo | th:onreset | th:onresize |
th:onscroll | th:onseeked | th:onseeking | th:onselect |