Spring MVC 视图层:Thymeleaf 与 JSP
在本文中,我们将比较同一个页面(一个订阅表单)在相同 Spring MVC 应用程序中的两个不同实现:一次使用 Thymeleaf,另一次使用 JSP、JSTL 和 Spring 标签库。
此处展示的所有代码都来自一个可运行的应用程序。你可以查看或下载源代码其 GitHub 仓库.
公共需求
我们的客户需要一个用于将新成员订阅到邮件列表的表单,包含以下两个字段:
- 电子邮件地址
- 订阅类型(接收所有邮件、每日摘要)
我们还需要该页面支持 HTML5 并完全国际化,从已经在我们的配置中设置好的资源文件中提取所有文本和信息。MessageSource
我们 Spring 基础设施中的对象。
我们的应用程序将有两个@Controller
控制器方法,它们将包含完全相同的代码,但会转发到不同的视图名称:
SubscribeJsp
对于 JSP 页面(即subscribejsp
视图)。SubscribeTh
对于 Thymeleaf 页面(即subscribeth
视图)。
我们的模型中将包含以下类:
Subscription
一个包含两个字段的表单支撑 Bean:String email
和SubscriptionType subscriptionType
.SubscriptionType
一个枚举类,用于表示表单中的subscriptionType
字段,其取值为ALL_EMAILS
和DAILY_DIGEST
.
(在本文中我们仅关注 JSP/Thymeleaf 模板代码。如果您想了解控制器代码或 Spring 配置的具体实现细节,请查看可下载包中的源代码)
使用 JSP 实现
这是我们的页面:

以下是我们的 JSP 代码,它同时使用了 JSTL (core
) 和 Spring (tags
和form
) 的 JSP 标签库:
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Spring MVC view layer: Thymeleaf vs. JSP</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all" href="<s:url value='/css/thvsjsp.css' />"/>
</head>
<body>
<h2>This is a JSP</h2>
<s:url var="formUrl" value="/subscribejsp" />
<sf:form modelAttribute="subscription" action="${formUrl}">
<fieldset>
<div>
<label for="email"><s:message code="subscription.email" />: </label>
<sf:input path="email" />
</div>
<div>
<label><s:message code="subscription.type" />: </label>
<ul>
<c:forEach var="type" items="${allTypes}" varStatus="typeStatus">
<li>
<sf:radiobutton path="subscriptionType" value="${type}" />
<label for="subscriptionType${typeStatus.count}">
<s:message code="subscriptionType.${type}" />
</label>
</li>
</c:forEach>
</ul>
</div>
<div class="submit">
<button type="submit" name="save"><s:message code="subscription.submit" /></button>
</div>
</fieldset>
</sf:form>
</body>
</html>
使用 Thymeleaf 实现
现在,让我们用 Thymeleaf 来实现同样的功能。这是我们的页面:

以下是我们的 Thymeleaf 代码:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring MVC view layer: Thymeleaf vs. JSP</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../css/thvsjsp.css" th:href="@{/css/thvsjsp.css}"/>
</head>
<body>
<h2>This is a Thymeleaf template</h2>
<form action="#" th:object="${subscription}" th:action="@{/subscribeth}">
<fieldset>
<div>
<label for="email" th:text="#{subscription.email}">Email: </label>
<input type="text" th:field="*{email}" />
</div>
<div>
<label th:text="#{subscription.type}">Type: </label>
<ul>
<li th:each="type : ${allTypes}">
<input type="radio" th:field="*{subscriptionType}" th:value="${type}" />
<label th:for="${#ids.prev('subscriptionType')}"
th:text="#{|subscriptionType.${type}|}">First type</label>
</li>
<li th:remove="all"><input type="radio" /> <label>Second Type</label></li>
</ul>
</div>
<div class="submit">
<button type="submit" name="save" th:text="#{subscription.submit}">Subscribe me!</button>
</div>
</fieldset>
</form>
</body>
</html>
需要注意的几点:
- 这个看起来比 JSP 版本更具 HTML 风格 —— 没有奇怪的标签,只有一些有意义的属性。
- 变量表达式 (
${...}
) 是 Spring EL,它在模型属性上执行;星号表达式 (*{...}
) 在表单支撑 Bean 上执行;哈希表达式 (#{...}
) 用于国际化;链接表达式 (@{...}
) 用于 URL 重写。(如果您想了解更多信息,请查阅“五分钟快速入门标准方言”教程)。 - 我们可以在模板中保留原型代码:例如,我们可以给第一个字段的标签设置一个
Email:
文本,并知道 Thymeleaf 在执行页面时会将其替换为键为subscription.email
的国际化文本。 - 我们甚至可以为了原型设计的目的,在第二个单选按钮旁边添加一个
<li>
标签。当 Thymeleaf 执行页面时这个标签会被删除。
我们来改变一下页面风格!
假设现在我们的页面已经编写完成,但我们突然决定不再希望提交按钮周围的区域使用绿色,而是改用一种淡蓝色。但我们并不确定哪种蓝色更合适,因此必须尝试多种配色方案后才能最终决定。
我们来看看每种技术需要采取的步骤:
使用 JSP 修改页面样式
步骤 1: 将应用程序部署到我们的开发服务器并启动它。由于 JSP 页面无法在不启动服务器的情况下渲染,所以这是一个必要条件。
步骤 2: 浏览页面直到找到需要修改的那个页面。通常,要修改的页面是我们应用程序中几十个页面之一,要访问到它很可能需要点击链接、提交表单和/或查询数据库。
步骤 3: 启动 Firebug、Dragonfly 或我们喜爱的浏览器内置网页开发工具。这将允许我们直接在浏览器 DOM 上进行样式修改,从而立即看到效果。
步骤 4: 进行颜色更改。可能需要尝试几种不同的蓝色色调,然后选择最满意的那个。

步骤 5: 将所做的更改复制粘贴到我们的 CSS 文件中.
完成啦!
使用 Thymeleaf 修改页面样式
步骤 1: 双击.html
模板文件本身,让浏览器打开它。作为一个 Thymeleaf 模板,它会正常显示,只是使用的是模板/原型数据(请注意订阅类型选项):

步骤 2: 使用我们喜欢的文本编辑器打开.css
文件。模板文件在其<link rel="stylesheet" ...>
标签中静态链接到 CSS(带有一个href
,在模板执行时 Thymeleaf 会将其替换为由th:href
生成的实际路径)。因此我们对该 CSS 所做的任何更改都会应用到浏览器当前显示的静态页面上。
步骤 3: 进行颜色更改。与使用 JSP 类似,我们可能需要尝试多个颜色组合,而这些更改只需按 F5 键即可在浏览器中刷新。
完成啦!
这就是巨大的差异!
步骤数量的差异其实并不是这里的关键(我们也可以对 Thymeleaf 模板使用 Firebug)。真正重要的是 JSP 所需每个步骤的复杂性、工作量和时间成本。必须部署和启动整个应用程序这一点使 JSP 处于劣势。
更进一步地想想,如果以下情况出现的话这种差异会如何演化:
- 我们的开发服务器不是本地的而是远程的。
- 改动不仅涉及 CSS,还涉及新增或删除一些 HTML 代码。
- 我们尚未实现在应用程序中所需的逻辑以实际到达该页面的功能。
最后一点非常重要:如果我们的应用程序还在开发阶段,显示此页面或其他前置页面所需要的 Java 逻辑还未完全正常运行,此时却需要向客户展示新的颜色?(甚至是让他们即时选择颜色!)…
而尝试将 JSP 作为静态原型使用会怎样呢?
好吧,你现在可以说,但为什么我们要启动应用程序来修改 JSP,而不是像处理 Thymeleaf 模板那样直接打开它?我们不能这样做吗?.
简短的回答是:不行。
但我们还是来试一下吧:当然,我们必须重命名我们的文件,使其文件名以.html
结尾,.jsp
而不是

什么?我们的页面去哪了?其实页面还在那里,但为了使我们的页面能作为 JSP 正常运行,我们不得不添加了许多 JSP 标签和特性,这些功能在由 Web 服务器执行时非常有效……但在同时,也使得页面不再是纯 HTML。因此浏览器也就无法正确显示它了。
再次回想一下,当我们双击 Thymeleaf 模板时它的样子:

显然不在同一个级别上,对吧?
支持 HTML5 吗?
不过,嘿——我们一开始就说页面要采用 HTML5,那么……为什么不使用一些酷炫的新 HTML5 表单相关功能呢?
例如,现在有一个<input type="email" ...>
输入类型,它会让浏览器检查用户输入的文本是否符合电子邮件地址的格式。此外,所有表单输入还新增了一个属性叫做placeholder
当输入框获得焦点(通常由用户点击)时,该属性会在字段中显示一段自动消失的文字提示。
听起来不错,是吧?不幸的是,并非所有浏览器都支持这一特性(截至 2011 年,Opera 11 和 Firefox 4 支持),但无论如何我们可以放心地使用这些特性,因为所有不支持这种输入类型的浏览器都会将其视为一个email
类型为text
的普通输入框处理,并且会静默忽略placeholder
属性,就像它们忽略 Thymeleaf 的属性一样。th:*
属性标记。
使用 JSP 实现 HTML5
Spring 3.1 之前版本的情况
直到 Spring 3.1 发布前,Spring MVC 的 JSP 标签库都不完全支持 HTML5,因此在此版本之前,除了使用纯 HTML 编写之外,没有其它方式能写出email 类型的输入标签,比如这样:
<input type="email" id="email" name="email" placeholder="your@email" value="" />
但这并不正确!在 Spring MVC 中,我们永远不应该以这种方式编写 JSP 输入字段,因为我们并没有正确地将输入与表单支持 Bean 的绑定属性相连。为了实现这一点,我们需要使用email
property of the form-backing bean. In order to do so, we would need to use an <s:eval/>
标签,它将应用所有必要的转换(比如属性编辑器),从而使我们的纯 HTML 标签表现得好像存在<sf:email/>
标签一样:
<input type="email" id="email" name="email" placeholder="your@email"
value="<s:eval expression='subscription.email' />" />
自从 Spring 3.1 开始
在 Spring 3.1 中仍然没有<sf:email ...>
标签,但现有的<sf:input ...>
标签允许我们指定一个type
属性并赋值为email
属性,一切就能立即正常运行,不仅能正确绑定我们的属性,还能集成 Spring MVC 的
<sf:input path="email" type="email" />
并且这将正确完成我们的表单绑定:-)
使用 Thymeleaf 实现 HTML5
Thymeleaf 完全支持 HTML5(甚至在 Spring 3.0 下也可以),所以我们只需要更改输入标签的type
类型placeholder
并添加一个属性编辑器更重要的是,作为原型展示时会被显示为普通的input
输入框——而这是sf:input
标签所做不到的:
<input type="email" th:field="*{email}" placeholder="your@email" />
完成啦!
