Spring MVC:从 JSP 和 Tiles 到 Thymeleaf

工程 | Michael Isvy | 2012 年 10 月 30 日 | ...

在视图层方面,Spring @MVC 提供了多种选择。在本文中,我们将首先讨论过去几年中最可能使用的视图层方式:JSP。我们将看到使用它们的好坏方式(纯 JSP、带自定义标签的 JSPApache Tiles)。

接下来我们将讨论一个名为 Thymeleaf 的新项目,您可以将其用作 JSP 的替代方案。

像往常一样,您可以在 github 上的相应应用中找到讨论的所有代码示例。

纯粹的 JSP

让我们从下面的代码示例开始

<html …> <body>
 <div style="padding-top: 50px;">
   <jsp:include page="../menu.jspx"/>
   <c:choose>
     <c:when test="${empty users}">
       Table is empty.
     </c:when>
     <c:otherwise>
      <table>
       <thead>
         <tr>
          <th> First Name </th>
          <th> Last name </th>
         </tr>
        </thead>
        <tbody>
        <c:forEach var="user" items="${users}">
        <tr>
          <td> <c:out value="${user.firstName}"/> </td>
          <td> <c:out value="${user.lastName}"/> </td>
        </tr>
        </c:forEach>
       </tbody>
     </table>
    </c:otherwise>
   </c:choose>
   <jsp:include page="../footer.jspx"/>
  </div>
 </body>
</html>

这个代码示例从某种意义上说并不是完全“最先进”的,因为它可以以完全相同的方式写在 8 年前。这是否意味着它已经过时了?让我们讨论一些可能的限制。

  1. 布局

我们使用了 <jsp:include /> 来包含 JSP 片段(页眉和页脚)。显然,有 <jsp:include /> 是好的,因为它避免了大量复制粘贴。然而,如果您有数百个 JSP 文件,您会发现自己不得不在所有 JSP 中复制粘贴那些 <jsp:include /> 标签。更好的做法是将所有布局信息外部化到一个专门的文件中。

  1. 冗长

我们的用户页面相当小,因为它只是简单地显示一个元素列表。我们已经有 50 行代码(上面的代码示例已略微 精简)。您可以想象如果我们需要显示大量内容时它会有多大。

  1. HTML/CSS 合规性

此页面不符合 HTML/CSS 规范。假设一个网页设计师已经对其进行了原型设计,为了使用侵入性的 JSP 语法,您将不得不完全重写它。我们将在谈论 ThymeLeaf 时回到这一点。

JSP 自定义标签

自定义标签是 Java EE 的一部分。它们允许您将 JSP 中重复的部分外部化,而无需编写任何 Java 代码。您只需创建一个专门的 .tagx 文件。

这里有一个例子


<jsp:directive.attribute name="title" required="true" rtexprvalue="true" />
<body>
 <div style="padding-top: 50px;">
  <jsp:include page="/WEB-INF/view/jsp/menu.jspx"/>
  <jsp:doBody />
  <jsp:include page="/WEB-INF/view/jsp/footer.jspx"/>
 </div>
 </body>

在示例应用程序中,此文件名为 mainLayout.tagx。它在我的文件系统上

上述示例中最重要的指令是 <jsp:doBody />。处理模板时,<jsp:doBody /> 将被替换为“主”JSP 中的内容。

在每个 JSP 中,我可以调用先前创建的标签。

如下所示,我们将 custom 命名空间关联到我们的 tags 文件夹。然后我们可以使用名为 mainLayout 的标签。


<div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:jsp="http://java.sun.com/JSP/Page"
 xmlns:custom="urn:jsptagdir:/WEB-INF/tags">
<custom:mainLayout title="${title}/">
 <c:choose>
…
 </c:choose>
</custom:mainLayout>

注意:如上代码示例所示,每个 JSP 都应该指定它使用的布局。如果我有多个布局,并且想将多个 JSP 从 mainLayout 迁移到 customLayout,我就需要编辑每个 JSP 文件并手动更改布局。我们稍后在讨论 Tiles 时会回到这一点。

自定义标签不仅可以将布局部分外部化,还能为您做更多事情。为了说明这一点,我创建了一个 simpleTable 标签,这样我就不必处理 <thead> 和 <tbody> 标签了。当表格为空时,它还会显示一条消息。我的 JSP 文件现在看起来像这样


<custom:mainLayout title="${title}/">
 <custom:simpleTable collection="${users}" headerLabels="First Name, Last Name">
   <c:forEach var="user" items="${users}">
     <tr>
       <td> <c:out value="${user.firstName}"/> </td>
       <td> <c:out value="${user.lastName}"/> </td>
     </tr>
   </c:forEach>
  </custom:simpleTable>
</custom:mainLayout>

您可以浏览 simpleTable.tagx 查看完整示例。

 

注意:我还应该提及由 Thibault Duchateau 创建的一个新项目,名为 Datatable4J。它在 jquery-datatables 的基础上提供了一组标签,因此允许您无需自己编写 Javascript 即可创建 AJAX 风格的数据表。文档写得很好,并且该项目正在积极开发中。

 

优缺点

标准标签有许多优点
  • 我可以用它们做更多的事情,而不仅仅是外部化布局信息。最终,它们可以轻松地使您的 JSP 文件比原本小 5 倍。
  • Eclipse/STS 对自定义标签的支持很好,因此您可以使用 CTRL+space 进行自动补全。
缺点
  • 文档不是最好的。
  • 尽管自定义标签已经相当简洁,但我回想不起过去几年它们有什么改进。
  • 使用自定义标签意味着您正在 Web 容器内部使用 JSP 引擎。因此,根据您使用的 Web 容器(Apache Tomcat、IBM Websphere、Oracle Weblogic…),可能会有一些微小的差异。
  • 视图层的单元测试也更困难。如果您对此话题感兴趣,可以查看 Spring MVC 测试框架

使用 Apache Tiles 外部化 JSP 布局

Apache Tiles 在十年前就因作为 Struts 1 的布局插件而闻名。它现在是一个独立的框架,并且与 Spring MVC 集成得很好。

首先,您应该声明相应的 Spring 配置


<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver"/>
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
 <property name="definitions">
 <list>
 <value>/WEB-INF/view/jsp/tiles.xml</value>
 </list>
 </property>
</bean>

在下面的示例中,我们将创建如下 3 个文件

布局文件

与 JSP 自定义标签一样,布局在一个专门的文件中描述。语法非常相似,只是我们使用的是 tiles 标签库。

<html xmlns:tiles="http://tiles.apache.org/tags-tiles" …>
<head> … </head>
 <body>
   <jsp:include page="/WEB-INF/view/jsp/menu.jspx"/>
   <tiles:insertAttribute name="main" />
   <jsp:include page="/WEB-INF/view/jsp/footer.jspx"/>
 </body>
</html>

layout.jspx

 

上述示例中最重要的指令是 <tiles:insertAttribute />。处理模板时,<tiles:insertAttribute /> 将被替换为“主”JSP 中的内容。然后我们将使用一个专门的文件(通常称为 tiles.xml),其中包含所有 tiles 定义,如下所示


<tiles-definitions>
 <definition name="tiles/*" template="/WEB-INF/view/jsp/03-tiles/layout.jspx">
  <put-attribute name="main" value="/WEB-INF/view/jsp/03-tiles/{1}.jspx" />
 </definition>
</tiles-definitions>

tiles.xml

[旁注 标题=通配符用法]过去,Apache Tiles 不支持通配符,因此每创建一个新的 JSP,我们就不得不在 tiles.xml 中复制粘贴一个新的定义。[/旁注]

根据上面的示例,视图“tiles/users”将使用模板 layout.jspx 解析为 /WEB-INF/view/jsp/users.jspx

在 JSP 内部,没有提及它使用的布局


<div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:jsp="http://java.sun.com/JSP/Page">
<table>
 <thead>
   <tr>
    <th> First Name </th>
    <th> Last name </th>
   </tr>
  </thead>
  <tbody>
  <c:forEach var="user" items="${users}">
   <tr>
     <td> <c:out value="${user.firstName}"/> </td>
     <td> <c:out value="${user.lastName}"/> </td>
   </tr>
  </c:forEach>
  </tbody>
 </table>
</div>

Apache Tiles 的方法与自定义标签类似,因此具有相同的优缺点。Apache Tiles 项目有一些活动,但肯定不如我们在下一节讨论的 ThymeLeaf 活跃。

Thymeleaf

Thymeleaf 将自己定义为一个 XML / XHTML / HTML5 模板引擎。

它不基于 JSP,而是基于一些普通的 HTML 文件,带有一些命名空间魔法。

第一步:我们将 Thymeleaf 与 Spring 集成。像往常一样,我们需要声明适当的视图解析器。


<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
  <property name="prefix" value="/WEB-INF/view/" />
  <property name="suffix" value=".html" />
  <property name="templateMode" value="HTML5" />
  <property name="cacheable" value="false" />
 </bean>
 <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
  <property name="templateResolver" ref="templateResolver" />
 </bean>
 <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="1" />
  <property name="viewNames" value="thymeleaf/*" />
 </bean>

现在让我们考虑一个简单的视图页面。


<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <link th:src="@{/style/app.css}" rel="stylesheet"/>
  </head>
  <body>
    <div th:if="${not #lists.isEmpty(users)}">
    <table>
     <thead> … </thead>
     <tbody>
<tr th:each="user : ${users}">
        <td th:text="${user.firstName}">John</td>
        <td th:text="${user.lastName}">Smith</td>
</tr>
     </tbody>
    </table>
</div></body></html>

users.html

我们可以注意到几点

-这是一个 html 文件!您实际上可以在网页浏览器中将其作为静态文件预览。这个功能非常适合原型设计 [1]。

  • 我们使用一个 专用命名空间,以便将静态 html 页面转换为动态视图。所有需要动态处理的部分都以“th:”作为前缀。

  • 使用 ‘@{…}’ 来引用上下文路径很简单。 这在纯 JSP 中非常容易出错 [2]。

  • ${users} 使用 Spring Expression Language 解析。如果我有一个表单,我会使用诸如 *{user.name} 的表达式来引用表单元素。

[1] 在本文中我们将不再深入讨论原型设计。但是,如果您想了解更多信息,可以阅读本教程 (http://www.thymeleaf.org/petclinic.html)。

[2] 在本文第一部分,使用 <jsp:include /> 时,我不得不使用相对路径 “../menu.jspx”。如果某天我将我的 JSP 文件移动到不同的文件夹,这将导致链接断开。

布局文件

现在让我们讨论如何将布局外部化到一个专门的文件中。

与 JSP 自定义标签和 Tiles 一样,您需要声明您的布局文件。在下面的代码示例中,您会找到 2 个片段

  • headerFragment 包含所有头部信息

  • menuFragment 包含我的菜单栏

这些名称不是强制性的,我可以拥有任意数量的片段。

在每个视图文件中,我可以使用 th:include 来引用片段,如下所示


<html xmlns:th="http://www.thymeleaf.org">
 <head th:include="thymeleaf/layout :: headerFragment">
 <!-- replaced with fragment content -->
 <!—- 'thymeleaf/layout' refers to /thymeleaf/layout.html on the filesystem -->
 </head>

 <body>

 <div th:include="thymeleaf/layout :: menuFragment">
 </div>
 <div th:if="${not #lists.isEmpty(users)}">
 <table>
   …
   <tbody>
     <tr th:each="user : ${users}">
      <td th:text="${user.firstName}">John</td>
      <td th:text="${user.lastName}">Smith</td>
     </tr>
   </tbody>
  </table>
 </div>
 </body>
</html>

在文件系统上我们有

优缺点

优点
  • Thymeleaf 是一个健康的开源项目:每月都有新功能推出,文档齐全,用户论坛响应迅速……
  • 如果您希望您的网页设计师能够阅读您的视图文件,它是理想的模板引擎
  • 使用的表达式语言(实际上称为标准方言 Standard Dialect)比 JSP 表达式语言强大得多。
  • 与 JSP 不同,Thymeleaf 非常适合 Rich HTML 电子邮件(参见 http://www.thymeleaf.org/springmail.html)。
缺点
  • Thymeleaf 尚没有相当于自定义标签(.tagx 文件)的功能。
  • 在此阶段,Thymeleaf 与 JSP 标签库不兼容。

结论

我们对比了 JSP 和 Thymeleaf 方法。如果您的应用程序使用了数百个 JSP 文件,我们并不是说您应该完全放弃它们并从头开始使用 Thymeleaf。但是,您可以考虑在 Web 容器之外的 HTML 页面中使用 Thymeleaf,例如用于 Rich HTML 电子邮件。

如果您正在开始一个新的项目,我们强烈建议您比较 Thymeleaf 和 JSP,以便确定哪种更适合您的需求。

此外,我的同事 Rob Winch 做了一个很棒的关于 Spring MVC 现代模板选项 的演示。除了 JSP 和 Thymeleaf,他还讨论了 Mustache 模板。

此博客文章使用的示例应用可在 github 上找到

获取 Spring 邮件列表

订阅 Spring 邮件列表,保持联系

订阅

领先一步

VMware 提供培训和认证,为您的进步加速。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部