Thymeleaf 3.0教程:3 使用文本
3.1一个多语言的欢迎页面
我们的第一个任务是为我们的杂货店网站创建一个主页。
这个页面的第一个版本将非常简单:只有一个标题和一个欢迎信息。这是我们的 /WEB-INF/templates/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}">Welcome to our grocery store!</p> </body> </html>
你会注意到的第一件事是这个文件遵循的是HTML5标准,它可以被任何现代浏览器正确显示,因为它不包含任何非HTML标签(浏览器忽略所有他们不理解的属性,比如th:text)。
但是您可能还注意到,这个模板并不是一个真正有效的HTML5文档,因为HTML5规范不允许我们在th:*表单中使用这些非标准属性。事实上,我们甚至在<html>标签中添加了xmlns:th属性,这绝对不是HTML5内置的:
<html xmlns:th="http://www.thymeleaf.org">
它对模板处理没有任何影响,但却像咒语一样阻止我们的IDE抱怨所有这些th:*属性缺少名称空间定义。
如果我们想让这个模板HTML5有效呢?很简单,切换到Thymeleaf的数据属性语法,属性名称使用数据前缀和连字符(-)分隔符,而不是分号:
<!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" data-th-href="@{/css/gtvg.css}" /> </head> <body> <p data-th-text="#{home.welcome}">Welcome to our grocery store!</p> </body> </html>
HTML5规范允许自定义数据前缀属性,因此,有了上面的代码,我们的模板将是一个有效的HTML5文档。
这两种符号完全等价且可互换,但是为了代码示例的简单性和紧凑性,本教程将使用命名空间符号(th:*)。此外,th:*符号更通用,并且在每种Thymeleaf模板模式(XML,TEXT…)中都允许,而data-符号仅在HTML模式下允许。
使用th:text和外部化文本
外部化文本是从模板文件中提取模板代码片段,以便它们可以保存在单独的文件中(通常为.properties文件),并且可以很容易地用用其他语言编写的等效文本替换它们(这一过程称为国际化或简称i18n)。文本的外部化片段通常被称为“消息”。
消息总是有一个标识它们的关键字,Thymeleaf允许您指定文本应该与带有#{...}语法:
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
我们在这里可以看到Thymeleaf标准方言的两个不同特征:
th:text属性,它评估其值表达式并将结果设置为主机标签的主体,有效地替代了“Welcome to our grocery store!”我们在代码中看到的文本。
标准表达式语法中指定的#{home.welcome}表达式,指示th:text属性要使用的文本应该是带有home.welcome键的消息,该消息对应于我们处理模板时使用的任何区域设置。
那么,这个外部化的文本在哪里?
Thymeleaf中外部化文本的位置是完全可配置的,它将取决于所使用的组织。通常情况下使用.properties文件,但是如果我们想从数据库中获取消息,我们可以创建自己的实现。
但是,在初始化期间,我们没有为模板引擎指定消息解析器,这意味着我们的应用程序正在使用由org.thymeleaf.messageresolver.StandardMessageResolver实现的标准消息解析器。
标准消息解析器希望在与模板同名的同一文件夹中的属性文件中找到/WEB-INF/templates/home.html的消息,例如:
/WEB-INF/templates/home_en.properties for English texts. /WEB-INF/templates/home_es.properties for Spanish language texts. /WEB-INF/templates/home_pt_BR.properties for Portuguese (Brazil) language texts. /WEB-INF/templates/home.properties for default texts (if the locale is not matched).
让我们看一下home_es.properties文件:
home.welcome=¡Bienvenido a nuestra tienda de comestibles!
这就是我们使用Thymeleaf构建项目所需的全部模板。让我们创建我们的Home控制器。
上下文
为了处理我们的模板,我们将创建HomeController类,实现我们之前看到的IGTVGController接口:
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()); } }
我们首先看到的是上下文的创造。Thymeleaf上下文是实现org.thymeleaf.context.IContext 接口的对象。上下文应该包含在变量映射中执行模板引擎所需的所有数据,还应该引用必须用于外部化消息的区域设置。
public interface IContext { public Locale getLocale(); public boolean containsVariable(final String name); public Set<String> getVariableNames(); 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
正如您在控制器代码中看到的,我们使用的是WebContext。事实上,我们必须这样做,因为ServletContextTemplateResolver的使用要求我们使用实现IWebContext的上下文。
WebContext ctx = new WebContext(request, response, servletContext, request.getLocale());
这四个构造函数参数中只有三个是必需的,因为如果没有指定,将使用系统的默认区域设置(尽管在实际应用中不应该让这种情况发生)。
我们可以使用一些专门的表达式从模板中的网络上下文中获取请求参数以及请求、会话和应用程序属性。例如:
${x} will return a variable x stored into the Thymeleaf context or as a request attribute. ${param.x} will return a request parameter called x (which might be multivalued). ${session.x} will return a session attribute called x. ${application.x} will return a servlet context attribute called x.
执行模板引擎
在我们的上下文对象准备好之后,现在我们可以告诉模板引擎使用上下文来处理模板(通过它的名称),并将它传递给响应编写器,以便可以将响应写入其中:
templateEngine.process("home", ctx, response.getWriter());
让我们使用西班牙语言环境来看看结果:
<!DOCTYPE html> <html> <head> <title>Good Thymes Virtual Grocery</title> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> <link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" /> </head> <body> <p>¡Bienvenido a nuestra tienda de comestibles!</p> </body> </html>
3.2 关于文本和变量的更多信息
未转义文本
我们主页最简单的版本似乎已经准备好了,但是有些事情我们还没有考虑到,如果我们有这样的消息呢?
home.welcome=Welcome to our <b>fantastic</b> grocery store!
如果我们像以前一样执行该模板,我们将获得:
<p>Welcome to our <b>fantastic</b> grocery store!</p>
这不是我们所期望的,因为我们的<b>标签已经被转义,因此它将显示在浏览器中。
这是th:text属性的默认行为。如果我们希望百里香叶尊重我们的超文本标记语言而不是转义它们,我们将使用不同的属性th:utext。
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
这将输出我们想要的信息:
<p>Welcome to our <b>fantastic</b> grocery store!</p>
使用和显示变量
现在让我们在主页上添加更多内容。例如,我们可能希望在欢迎信息下方显示日期,如下所示:
Welcome to our fantastic grocery store! Today is: 12 july 2010
首先,我们必须修改我们的控制器,以便将该日期添加为上下文变量:
public void process( final HttpServletRequest request, final HttpServletResponse response, final ServletContext servletContext, final ITemplateEngine templateEngine) throws Exception { SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); Calendar cal = Calendar.getInstance(); WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); ctx.setVariable("today", dateFormat.format(cal.getTime())); templateEngine.process("home", ctx, response.getWriter()); }
我们在我们的上下文中添加了一个字符串变量,现在我们可以在模板中显示它:
<body> <p th:utext="#{home.welcome}">Welcome to our grocery store!</p> <p>Today is: <span th:text="${today}">13 February 2011</span></p> </body>
如您所见,我们仍然在使用th:text属性(这是正确的,因为我们想要替换标记的主体),但是这次语法有点不同,而不是#{...}表达式值,我们正在使用${...}一个。这是一个变量表达式,它包含一个用称为OGNL(对象图导航语言)的语言表示的表达式,该表达式将在我们之前讨论过的上下文变量图上执行。
${today}表达式简单地表示“获取并输出变量today的值”,但是这些表达式可能更复杂(例如${user.name}表示“获取user对象的变量,并调用其getName()方法”)。
属性值有很多可能性:消息、变量表达式……等等。下一章将向我们展示所有这些可能性。