入门
本教程讲述如何使用 Tiles 框架来创建可重用的表示组件。(在最初创建它时,Tiles 框架被命名为 Components。 后来改变了名称是因为“components(组件)”代表了太多不同的东西,不过原先的名称的精髓仍然得到了保留。) 然而,除了站点布局之外,使用 tile 还能做其他许多事情。例如,您可以划分表示层以更好地重用布局、HTML以及其他可视组件。
本教程力图揭示使用 Tiles 框架的基础,然后让您的知识再上一个台阶。当完成本教程的学习时,您将能够使用更高级的 Tiles 特性来创建可重用组件。
注意:贯穿本教程,我们交替使用术语 tile 和 页面,因为任何 Web 资源都可以是 tile。 一个 tile 布局代表一种特殊类型的 tile,即可以用来在它内部放置其他 tile。 一个 tile 布局可以用作另一个 tile 布局内的 tile。
明确地说,本教程:
- 定义 Tiles 框架和体系结构。
- 介绍 Tiles 体系结构以及它是如何与 Struts 集成的。
- 澄清一些关键 Tiles 概念。
- 展示如何生成 tile 以及将 tile 用作站点模板。
- 展示如何使用 XML 和 JavaServer Pages (JSP) 中的 tile 定义。
- 定义 tile 范围(scope)以及如何将对象移进和移出 tile 范围。
- 使用属性列表。
- 展示如何嵌套 tile。
- 展示如何生成 tile 以及将 tile 布局用作小型可视组件。
- 展示如何细分定义。
- 为 tile 创建控制器。
- 展示如何将 tile 用作
ActionForward
。
如果您发现自己在每个页面上都要编写三行相同的 JSP 代码,或者您想容易地定义复杂的模版布局,那么您就会从本教程中获益。
本教程假设您完全理解 Java 编程、MVC(Model-View-Controller,模型-视图-控制器)、Model 2 和 JSP 技术。虽然良好的 Struts 背景会让您从本教程中获得最大好处,不过只要您精通 JSP 编程,就应该能够理解本教程讲述的大多数内容。
为完成本教程的学习,您将需要:
- 符合 JSP 1.1、1.2 或 2.0 版的 servlet/JSP 容器。 Apache Tomcat 3.x 或更高版本是一个优秀的选择。注意:本教程的例子是使用符合 JSP 1.2 的容器来编写的。
- Tiles 框架。可作为Struts 1.1下载包的一部分或作为单独的组件从Tiles Web 站点获得该框架。
- 源代码。我已提供了两个版本:一个带 jar 文件的版本和为窄带用户准备的一个不带 jar 文件的版本。 Struts 附带了一个空 war 文件
struts-blank.war
(在webapps
目录下),它说明了您需要哪些配置文件和 jar 文件,以及通常将它们放在哪里。您将对本教程中的例子代码使用相同的结构。
请参阅参考资料以了解关于这些材料和附加参考资料的信息。
Tiles 框架和体系结构
Tiles 框架彻底揭示了 jsp:includes
内部的概念 ―― 从而允许您更灵活地创建可重用的页面。使用 Tiles 框架,开发人员能够通过组合可重用的 tile 来构建页面。您应该将 tile 看作是可视组件。
Tile 布局是允许在其上放置其他 tile 的特殊 JSP 页面。 Tile 布局控制了 tile 在页面上的放置位置。从许多方面看来,tile 布局都和模板布局类似。事实上,如果以前使用过 Struts,那么您会注意到 Tile 框架与模板自定义标签库向是后兼容的。
本教程中出现的术语初看起来可能有点难以招架,因此在更详细地讨论 Tiles 框架之前,让我们首先定义一些重要术语。
术语词汇表
-
Tiles Struts 用来创建表示组件的模板框架。
页面 tile 布局包括的 Web 资源。
Tile 同页面。
区域 tile 布局中插入其他 tile 的范围。 区域拥有诸如页眉、页脚之类的逻辑名称。
Tile 布局 描述其他页面应该定位在何处的 JSP 页面。Tile 布局充当模板,定义了插入其他 tile 的区域。 一个 tile 布局可以是另一个 tile 布局的 tile。
定义 定义用于调用某个 tile 布局的参数。
从某些方面看来,tile 布局工作起来就像一个显示函数。要使用某个 tile 布局,可使用 tiles:insert
标签来调用它。调用 tile 布局时要向它传递参数。这些参数将成为该 tile 布局的属性;例如,参数将放入 tile 范围。
调用 tile 时传递的参数可以是其他 JSP 页面或 Web 资源,您可以将它们插入布局中的预定义位置(称为 区域)。参数还包含能够插入 tile 布局的字符串。事实上,可以将许多类型的对象作为参数传递给 tile。这些参数会成为仅对该 tile 可用的 tile 范围内的属性。
tile 范围 类似页面范围,因为 tile 范围比请求范围更特殊化。 tile 范围允许 tile 用户给 tile 传递参数(称为属性)。tile 范围允许您传递仅对该 tile 布局或 tile 可用的变量(称为属性)。 特殊自定义标签允许您将属性从 tile 范围复制到页面、请求、会话或应用程序范围,或者将属性作为包含的 Web 资源来显示。
有些编程语言,比如 C++、Visual Basic 和 Python,允许您向函数和方法传递默认参数。为进一步扩展这个显示函数,Tiles 框架还允许您向 tile 布局传递默认参数。为此,您必须定义一个 tile 定义 。 Tile 定义允许您定义 tile 的默认参数。Tile 定义(definition)可以在 JSP 代码或 XML 中定义。
像类扩展其他类一样,定义可以扩展其他定义。通过使用定义和 tile 布局,您能够创建可重用的显示组件。
可以结合 Struts 使用 Tiles,也可以在没有 Struts 的情况下使用 Tiles。要结合 Struts 使用 Tiles,您将使用 Struts 附带的 Tiles 标签库。 此外,Tiles 框架包括它自己的 RequestProcessor
,用于将 tile 布局作为 ActionForward
来处理――从而允许您转到 tile 定义而不是转到 JSP 页面。Tile 是通过在它的 RequestProcessor
中重写processActionForward
来实现这点的。
典型的 tile 布局可能为页眉、页脚、菜单和正文定义矩形区域,如图 1 所示。
图 1 所示的区域可以映射到类似图 2 所示的某个 Web 站点。
注意,只需传递正确的参数,就能够容易地重新定义这个应用程序的可重用部分。 例如,雇员清单可能使用相同的页眉和页脚,但是使用不同的菜单和正文,同时仍然能够使用 tile 布局所定义的全部通用布局区域。 这样允许对不同的内容重用相同的 tile 布局。 与包括 HTML 标记不同的是,您将在标记中包括内容。
1. Tile 布局
如果站点能够重用相同的布局(使用 HTML 表格来实现)和图像,而不必重复相同的 HTML 代码,这样不是很好吗?
Tile 在为站点创建共同的外观方面特别出色。话虽这样说,许多开发人员并没有认识到 Tiles 在创建用 JSP 实现的可重用组件方面同样也很出色。
如果您发现自己在多个页面上重复相同的 HTML 代码,就可考虑对那些页面使用 tile 布局。类似地,如果在不同页面上的不同地方使用相同的 HTML 或 JSP 标签,这种情形也很适合使用 tile 来创建小型可视组件。
作为 Tiles 框架的一个例子,下面将重构一个简单的股票报价应用程序来利用 tile 布局,如图 3 所示。
这个简单的示例应用程序主要包含一个股票报价页面,它具有一个接受单个参数(即股票代码)的表单(index.jsp
)。 另一个页面显示股票报价的值(quote.jsp
)。
研究一下下面这两个代码清单。您将重构它们以使用各种各样的 tile 布局。
index.jsp
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <html> <head> <title>Stock Quote</title> </head> <body> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td> </td> </tr> <tr bgcolor="#36566E"> <td height="68" width="48%"> <div align="left"> <img src="images/hp_logo_rickhightower.gif" width="220" height="74"> </div> </td> </tr> <tr> <td> </td> </tr> </table> <html:form action="Lookup"> <table width="45%" border="0"> <tr> <td><bean:message key="app.symbol"/>:</td> <td><html:text property="symbol"/></td> </tr> <tr> <td colspan="2" align="center"><html:submit /></td> </tr> </table> </html:form> </body> </html>
quote.jsp
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <html> <head> <title>Stock Quote</title> </head> <body> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr> <td> </td> </tr> <tr bgcolor="#36566E"> <td height="68" width="48%"> <div align="left"> <img src="images/hp_logo_rickhightower.gif" width="220" height="74"> </div> </td> </tr> <tr> <td> </td> </tr> <tr> <td> </td> </tr> <tr> <td> </td> </tr> <tr> <td> <bean:message key="app.price"/>: <%= request.getAttribute("PRICE") %> </td> </tr> <tr> <td> </td> </tr> </table> </body> </html>
欲学习如何使用 Tiles 框架,您首先必须编写一个 tile 布局,然后重构上述两个例子页面,以便它们不必重复如此多的 HTML 代码。
为了创建一个 tile 布局,您需要做以下事情:
- 找出两个页面的相似之处。
- 创建一个新的布局页面。
- 创建两个新的内容页面,它们仅包含 和 之间的不同之处。
EmployeeListing.jsp
DeptListing.jsp
- 将 tile 布局插入页面 ―― 也就是让 和 在它们的页面中插入 tile 布局,并将内容作为参数传递,同时传递其他必要的参数(比如标题)。
EmployeeListing.jsp
DeptListing.jsp
由于找出两个页面之间的相似之处需要 HTML 布局和 Web 站点适用性方面的技能,事实证明这项工作更像一门艺术,而不是像一门科学。由于某些原因,拥有紫色的头发和纹身是有所帮助的。如果您不这样认为,可以问我的朋友 Boo。
本教程重点集中于 Struts,而不是 HTML 布局和 Web 站点适用性方面的必要技能。 因此,您不会了解关于纹身和紫色头发方面的内容。事实上,例子中的 HTML 布局是刻意简单化的,以防分散我们对 Tiles 框架的注意力。
一旦找出了页面之间的相似之处(这是困难的部分),您就能够创建新的布局页面(这是容易的部分)。为了创建一个 tile 布局,您必须做以下事情:
- 使用标签库指令将 tile 标签库导入 JSP,同时导入需要的其他任何标签库。
- 使用字符串参数来显示像页面这样使用 标签的内容。
tiles:getAsString
- 使用 标签将 tile 插入布局的适当区域。
tiles:insert
- 使用
tiles:put
标签向内部 tile 传递任何需要的参数 ―― 这个标签是tiles:insert
标签的子标签。
将 tile 标签库导入 JSP,同时导入需要的其他任何标签库,如下所示(siteLayout.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %
注意:要使用 tile 标签库,不要忘了包括 web.xml
文件中的标签库:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<taglib> <taglib-uri>/WEB-INF/struts-tiles.tld</taglib-uri> <taglib-location>/WEB-INF/struts-tiles.tld </taglib-location> </taglib>
接下来使用字符串参数显示诸如页面标题之类的内容。您不仅需要更改页面的内容,而且还需要更改出现在浏览器中的标题。为此,需要传入 tile 布局将要使用的标题:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<html> <head> <title> <tiles:getAsString name="title" ignore="true"/> </title> </head>
注意该代码中使用了tiles:getAsString
标签来显示字符串参数。您不仅能够传递字符串参数,而且能够传递要插入这个页面的其他页面。这里假设调用 JSP 页面向这个 tile 布局传递了一个标题;否则,标题将是空白。
注意: ignore
属性:
ignore
属性如果为 true,这意味着在缺失该属性的情况下忽略它。否则,如果 ignore
属性为 false,那么在没有传递该参数的情况下,Tiles 框架将抛出异常,页面将不会显示出来(false 是默认值)。
要插入内容 JSP,可使用 tiles:insert
标签,它插入该框架作为 tile 来引用的任何页面或 Web 资源。这个标签实际上在 tile 布局中定义了一个区域。 记住,tile 布局的目标是将 tile 布置到该布局中。下面是向该布局插入一个 tile 的例子:
<tiles:insert attribute="content"/>
上面这个例子非常简单。如果想要插入一个 tile,并向它传递当前页面范围内的项,那该怎么办呢?例如,使用 Tiles 框架给 header.jsp
传递一个标题参数(在 tile 范围内)是可以做到的。
在插入 tile 的任何时候,您都可以选择性地给它传递参数。传递给 tile 的参数将被放入该 tile 的标题范围(称为“标题属性”)。例如,除了让标题显示在浏览器的标题栏之外,可能还希望该标题出现在页面的页眉区域。
header.jsp
文件将完成这个任务。虽然标题变量在该 tile 布局页面范围之内,但它不在该 tile 布局所插入的 tile 的范围之内。脆弱方法每个 tile 和 tile 布局都获取它自己的环境 ―― 也就是它自己的 tile 范围。因而,您必须像下面这样给页眉 tile 传递该 tile 变量:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:insert attribute="header" ignore="true"> <tiles:put name="title" beanName="title" beanScope="tile"/> </tiles:insert>
tiles:put
标签将这个 tile 布局范围内的 tile 参数放进页眉 tile 的范围。然后页眉 tile 就能够像 tile 布局所做的那样,通过 tiles:getAsString
标签来使用这个参数。参数名称就是页眉的 tile 范围内的属性名称。 bean 参数是当前范围内(siteLayout.jsp
)的 bean 的名称。 beanScope 是您在其中查找这个属性的范围(可能的值是页面、tile、请求、会话和应用程序)。 您可以从任何范围向该 tile 传递 bean。
接下来,您会看到 quote.jsp
和 index.jsp
将要使用的这个新布局页面(siteLayout.jsp
)的完整清单:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <html> <head> <title> <tiles:getAsString name="title" ignore="true"/> </title> </head> <body> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr bgcolor="#36566E"> <td height="68" width="48%"> <div align="left"> <img src="images/hp_logo_rickhightower.gif" width="220" height="74"> </div> </td> </tr> <tr> <td height="68" width="2000"> <tiles:insert attribute="header" ignore="true"> <tiles:put name="title" beanName="title" beanScope="tile"/> </tiles:insert> </td> </tr> <tr> <td> <div align="center"> <tiles:insert attribute="content"/> </div> </td> </tr> <tr> <td> <tiles:insert attribute="footer" ignore="true"/> </td> </tr> </table> </body> </html>
请花点时间研究一下上面的代码。注意 tile 是如何插入不同区域的(页眉、页脚、内容),以及如何利用 HTML 布局来为 tile 定义区域,从而为应用程序定义完整的布局。
现在已经定义好了使用 tiles 的 tile 布局,您需要使用该布局。 index.jsp
和 quote.jsp
都将使用同一个布局。虽然这对两个页面来说似乎是大量的工作,但是对于真实的 Web 应用程序,你可能会对 20 个或更多的页面使用同一个布局。 通过这种方式,您不必在 20 个位置重复 HTML 或包括 JSP 片断。
注意:为什么不就使用 jsp:include
呢?
在适当的位置包括 JSP 片断是重用 HTML 的脆弱方法。设想一下包括相同的 5 个 JSP 片断的 20 个页面 ―― 您必须重复 100 次。
为了使用 tile,您需要执行以下步骤:
- 使用
taglib
指令导入 tile 标签库。 - 使用
tiles:insert
标签来将 tile 布局插入当前页面。 - 使用
tiles:put
来传递字符串参数。 - 使用
tiles:put
来传入参数 tile。
通过使用 tile 布局,您能够在一个位置中将站点布局所需要的整个 HTML 外部化,然后只需将它插入每个页面。观察一下下面的例子,它显示了如何把 tile 布局插入 index.jsp
:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <tiles:insert page="/siteLayout.jsp" flush="true"> <tiles:put name="title" type="string" value="Get Rick Hightower Stock Quote"/> <tiles:put name="header" value="/header.jsp"/> <tiles:put name="footer" value="/footer.jsp"/> <tiles:put name="content" value="/indexContent.jsp"/> </tiles:insert>
现在,当您想要在 quote.jsp
中做相同的事情时,只需更改内容和页眉。
您需要使用插入标签来调用 tile 布局(显示函数)。(注意用来将 tile 布局插入当前页面的 tiles:insert
标签):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:insert page="/siteLayout.jsp" flush="true">
page 属性指定了上一节中定义的 tile 布局。如果 flush
属性被设置为 true,这个 tile(以及到目前为止的页面)将在页面的其余部分之前(或在缓冲区满而迫使执行刷新时)写到浏览器。
要更改 quote.jsp
和 header.jsp
之间的页面 tile,可使用子标签 tiles:put
:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:put name="title" type="string" value="Get Stock Quote"/>
注意 tiles:put
是如何向 tile 布局传递字符串参数的。 tiles:put
的 name
属性标签指定了参数名称。tiles:put
的type
属性指定了参数的类型。最后,value
参数用于传递 title
属性的值。这允许您在使用 tiles:insert
标签来调用 tile 布局(显示函数)时,把简单的字符串作为参数来传递。这些参数将成为该 tile 布局属性;也就是被插入该 tile 布局的 tile 范围。
注意您是如何将三个 tile 作为页眉、页脚和内容参数来传递的( header.jsp
、footer.jsp
和 indexContent.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:put name="header" value="/header.jsp"/> <tiles:put name="footer" value="/footer.jsp"/> <tiles:put name="content" value="/indexContent.jsp"/>
header.jsp
页面将被插入该 tile 布局的页眉区域。 footer.jsp
页面将被插入该 tile 布局的页脚区域。indexContent.jsp
页面将被插入该 tile 布局的内容区域。 如果想插入不同的内容和 tile,只需改变内容参数的值。
注意用于 index.jsp
的表单不再驻留在 index.jsp
中。该表单现在驻留在 indexContent.jsp
中,如下面所列出的:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <html:form action="Lookup"> <table width="45%" border="0"> <tr> <td><bean:message key="app.symbol"/>:</td> <td><html:text property="symbol"/></td> </tr> <tr> <td colspan="2" align="center"><html:submit /></td> </tr> </table> </html:form>
除了将 tile 指定为 JSP 页面外,您还能够在 tiles:put
标签的正文内将文本作为 tile 来传递。quote.jsp
所做的正好就是这样:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <tiles:insert page="/siteLayout.jsp" flush="true"> <tiles:put name="title" type="string" value="Rick Hightower Stock Quote"/> <tiles:put name="header" value="/header.jsp"/> <tiles:put name="footer" value="/footer.jsp"/> <tiles:put name="content" type="string"> <bean:message key="app.price"/> <%= request.getAttribute("PRICE") %> </tiles:put> </tiles:insert
注意 tiles:put
标签的标签体包含 quote.jsp
的内容。其他每项内容都是该布局所定义的,都与上一个例子中使用的 tile 相同。这样的优点是能够减少系统中的 JSP 页面的数目。 关于哪种方法工作得最好,很久以来一直存在争议,我的结论是它取决于 put 标签体中有多少代码。
您看到这里存在的问题了吗?存在这样一条规则:不要重复您自己(Don't repeat yourself,DRY),而您已经违背了这点规则。您知道为什么吗?
2. Tile 定义
遗憾的是,quote.jsp
和 index.jsp
都违背了 DRY 规则,它们都重复定义了页眉和页脚参数。由于它们都使用相同的参数值,因此不必在两个页面上重复相同的参数是很理想的。
设想有这样一个真实的应用程序,其中的 tile 布局包括更多的区域(比如说 8 个)和使用该 tile 布局的更多页面。您会发现每次想使用某个 tile 布局时都要重复每个参数是一件很痛苦的事情。既然大多数页面都将使用相同的页眉和页脚,那么在单个位置而不是在每个页面定义它们将会带来好处。
回顾一下前面的显示函数类比,tile 布局在某些方面类似一个显示函数。 您使用 tiles:insert
来调用 tile 布局,并且使用 tiles:put
来传入参数。参数是能够插入 tile 布局区域的其他 JSP 页面或字符串。
您现在需要定义对应于页眉和页脚区域的默认参数的能力。Tile 框架还允许您使用定义(definition)来给 tile 布局传递默认参数。
在本节中,您将学习如何创建和使用定义。定义(definition)定义了 tile 布局的默认参数。定义(definition)可以在 JSP 代码或 XML 中定义。在结束本节的学习时,您将能够同时使用这两种方法创建定义。
您将发现使用 JSP 页面来创建定义是最容易的方法,因为它需要最少的配置。
要创建一个 JSP 定义,请执行以下步骤:
- 使用
taglib
指令导入 tile 标签库。 - 使用
logic:notPresent
标签来确保该定义仅被定义一次。 - 使用
tiles:definition
标签来定义该定义,同时传递定义了 tile 布局的 JSP 页面以及新创建的定义的范围。 - 使用
tiles:put
标签定义默认参数。
在下面的清单中,siteLayoutDefinition.jsp
定义了这样一个定义,它使用 siteLayout.jsp
作为 tile 布局,并定义了页眉和页脚的默认参数(以及其他参数):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <logic:notPresent name="siteLayoutDef" scope="application"> <tiles:definition id="siteLayoutDef" page="/siteLayout.jsp" scope="application"> <tiles:put name="title" type="string" value="Rick Hightower Stock Quote System"/> <tiles:put name="header" value="/header.jsp"/> <tiles:put name="footer" value="/footer.jsp"/> <tiles:put name="content" type="string"> Content goes here </tiles:put> </tiles:definition> </logic:notPresent>
tiles:definition
标签定义了一个ComponentDefinition(org.apache.struts.tiles.ComponentDefinition)
类型的 JavaBean。 ComponentDefinition
具有用于传递给它的所有属性的 getter 和 setter 方法。logic:notPresent
标签通过在定义之前检查它是否已经在范围中,从而确保 ComponentDefinition
对每个应用程序仅创建一次。
注意: 默认设置可能会带来麻烦。
注意您还要为内容和标题定义默认参数。然而,这被认为是很糟糕的做法。为什么这样说呢?如果有人忘了使用这个标题,他们将取得默认值。由于标题应该随每个页面而改变,您不应该为它定义默认值。那样的话,如果有人忘了传递这个标题,tile 布局就会失败。为了使 tile 在这种情况下失败,您需要做以下两件事情:
- 不要在定义中定义默认值。
- 在使用
tiles:insert
标签定义 tile 布局中的区域时,不要将ignore
设置为 true。
Tile 定义的使用类似于直接使用 tile 布局。唯一的区别:您将指定定义而不是指定 tile 布局 JSP 页面,并且您将使用 tiles:put
传入更少的参数。
要使用 tile 定义,请执行以下步骤:
- 使用
taglib
指令导入 tile 标签库。 - 使用 jsp:include 来包含定义该定义的 JSP 页面。
- 使用 tiles:insert标签,不过要指定“定义 bean(definition bean)”名称和范围而不是指定 tile 布局页面。
- 使用 tiles:put属性来仅指定标题和内容(不指定页眉和页脚)。
下面是一个使用 tile 定义的例子(index2.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <jsp:include page="siteLayoutDefinition.jsp"/> <tiles:insert beanName="siteLayoutDef" beanScope="application"> <tiles:put name="title" type="string" value="Get Rick Hightower Stock Quote 2"/> <tiles:put name="content" value="indexContent2.jsp"/> </tiles:insert>
注意代码中使用了 beanName
属性 siteLayoutDef
来指定上一节定义的定义。beanName
属性值对应于上一节中的 bean 定义的 id
属性值。请注意例子中使用 tiles:put
来指定两个参数而不是四个参数,这意味着更少的键入工作和维护更少的代码 ―― 这就是 DRY 的乐趣。
您看到这里存在的问题了吗?您必须为定义创建一个 JSP 页面(siteLayoutDefinition.jsp
),为内容创建一个页面(indexContent.jsp
),为 index.jsp
本身创建一个页面,为布局创建一个页面(siteLayout.jsp
),为页眉创建一个页面,以及为页脚创建一个页面。吆! 您总计要创建 6 个而不是 1 个 JSP 页面(而这还只是一个 简单 的例子)。就算您获得了可重用性,但这是以牺牲简单性为代价的。
关于这个例子的另一个不可思议之处在于该定义本身。JSP 页面的本意是为了以文档为中心的方式表达可视内容。然而,该定义没有任何内容本质上是可视的。事实上,它主要不过就是配置。一个布局可能有多组定义,因此您会发现每个定义都有一个 JSP 页面真是一件麻烦事情。将所有配置保留在单个位置很不错,但是如何做到这点呢?
注意:对定义使用 jsp:include
而不是 @page include
。
如果以前使用过 tile,您也许已经看到过使用包含指令(@page include
)而不是使用动态包含操作(jsp:include
)的例子。 我更喜欢 jsp:include
,因为包含指令在编译时执行,而且,除非包含它的页面改变了,否则新的 JSP 定义就不会重新定义。使用 jsp:include
操作吧,它会帮您省去一些开发方面的麻烦。事实证明两者之间的性能差别是可以忽略的(指令要稍微快一点),但是过时的 JSP 定义的痛苦使人们躲避去使用它。
XML 定义解决了非可视化的 JSPpage 暴露的问题。与其为每个 JSPpage 定义一个定义,您可以在单个配置文件中定义所有配置。然而在能够开始使用 XML 定义之前,您需要首先使用对应的 Struts 的 Tiles 插件:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<plug-in className="org.apache.struts.tiles.TilesPlugin"> <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"/> <set-property property="moduleAware" value="true"/> <set-property property="definitions-parser-validate" value="true"/> </plug-in>
您需要向 struts 配置文件添加上述代码。注意 definition-config
属性指定了将包含基于 XML 的定义的 XML 文件。这段代码还指定这个 tile 引擎是模块感知的(module-aware),并指定它验证 XML 文件:
一旦定义了该插件,创建 XML 定义就变得容易了。 您只需在 tile 定义文件(例如 tiles-def.xml
)中添加另一个条目:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles-definitions> <definition name="siteLayoutDef" path="/siteLayout.jsp"> <put name="title" value="Rick Hightower Stock Quote System"/> <put name="header" value="/header.jsp"/> <put name="footer" value="/footer.jsp"/> <put name="content" type="string"> Content goes here </put> </definition>
根元素是 tiles-definition;
―― 这个模块的所有 tile 定义都将定义在 tiles-definition 元素内。
definition 元素指定一个 tile 定义。上面定义的定义在功能上等价于前面定义的 JSP 版本。注意该定义的属性稍有区别:使用 name
而不是 id
,以及使用 path
而不是 page
。(很气人,不是吗?)如果您知道如何定义一个基于 JSP 的定义,那么定义基于 XML 的定义将证明只是小孩子玩的游戏,因为它们在形式和功能上几乎是完全相同的。
现在已经定义好了 XML 定义,您需要更改 quote.jsp
和 index.jsp
以使用它。事实证明该定义的使用和以前几乎没有区别:唯一的区别是传递给 tiles:insert
标签的属性,如下所示(index3.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <tiles:insert definition="siteLayoutDef"> <tiles:put name="title" type="string" value="Get Rick Hightower Stock Quote 3"/> <tiles:put name="content" value="indexContent3.jsp"/> </tiles:insert>
注意您现在使用 definition 属性来指定 tile 定义文件(tiles-def.xml
)中创建的定义,而不是使用 beanName
和beanScope
。还要注意您需要在定义 JSP 中使用 jsp:include
或 logic:notPresent
。
一旦您转变思路,开始使用 XML 定义而不是使用 JSP 定义,那么 tile 的使用将变得更容易一些。您将只需编写更少的代码和维护更少的非可视化 JSP 页面。
1. 高级 tile 主题
记住 Tiles 框架定义了一个称为“tile 范围”的附加范围,它与页面范围类似。像页面范围一样,tile 范围比请求范围更私有。Tile 范围允许 tile 用户给 tile 传递变量(称为参数)。本质上,它使得页面像函数一样可调用。
记住,jsp:include
允许您调用一个页面,同时传递给它一个请求参数(jsp:param)。tiles:insert
标签类似jsp:include
,不过前者更强大。 tiles:insert
标签允许您调用一个页面,同时向它传递子页面(称为 tile)和属性。Tile 范围本质上允许您传递仅对该 tile 布局可用的变量。
如果知道 tile 范围是如何实现的,您就能够理解它。我曾创建过一个名为 listTileScope 的调试实用程序,它允许我打印出 tile 范围中的变量,如下面的代码片段所示:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
import org.apache.struts.taglib.tiles.ComponentConstants; import org.apache.struts.tiles.ComponentContext; public static void listTileScope(PageContext context) throws JspException, IOException { JspWriter out = context.getOut(); ComponentContext compContext = (ComponentContext)context.getAttribute( ComponentConstants.COMPONENT_CONTEXT, PageContext.REQUEST_SCOPE); out.println("--- TILE Attributes --- <br />"); if (compContext!=null){ Iterator iter = compContext.getAttributeNames(); while(iter.hasNext()){ String name = (String)iter.next(); Object value = compContext.getAttribute(name); printNameValueType(name, value, out); } }else{ out.println("---TILE Attributes NOT FOUND---<br />"); } out.println("--------------------------- <br />"); } private static void printNameValueType( String name, Object value, JspWriter out) throws IOException{ if (value !=null){ out.println( name + " = " + value + " type (" + value.getClass().getName()+ ") " + "<br /><br />"); }else{ out.println(name + " = " + value + "<br /><br />"); } }
注意 ComponentContext
类实现了 tile 范围。 ComponentContext
类位于 ComponentConstants.COMPONENT_CONTEXT
键下面的请求范围中。这种 tile 机制确保每个 tile 获得它自己的组件环境。
嵌套的 tile 不会和它们的父亲共享相同的 tile(我费了好大的劲才了解到这点)。当前 tile 的 tile 范围已在显示嵌套的 tile 之前得到保存。在嵌套的 tile 结束之后,父亲的 tile 范围将恢复到请求中。这个神奇的特性是在 InsertTag (org.apache.struts.taglib.tiles.InsertTag)
类的嵌套类 InsertHandler
中实现的。
到目前为止,您已经向对应于子 tile 的 tile 布局传递过属性或传递简单的字符串。可以将您希望的任意 bean 类型作为属性传入 tile 布局。然后在那个 tile 布局内使用该属性。
假设您的应用程序具有这样一个操作,它将一个 User
对象放入会话范围,或许是在用户登录系统之后:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Default target to success String target = new String("success"); // If login successful. UserDomainObject user = new UserDomainObject(); ... request.getSession().setAttribute("user", user); return (mapping.findForward(target)); }
接下来您将该用户传递到您正在插入的 tile。这个例子将使用您在 tile 布局(siteLayout2.jsp
)内所使用的那个 tile:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:insert attribute="header" ignore="true"> <tiles:put name="title" beanName="title" beanScope="tile"/> <tiles:put name="user" beanName="user" beanScope="session"/> </tiles:insert>
该 tile 布局通过指定 session
的范围和 user
的 bean 名称,从而将 user
bean 传递给页眉 tile。使用这项技术,您可以在任何 JSP 范围中将任意 bean 传递给 tile 或 tile 布局,这样该 tile 范围就变成了另一个范围。这与以前并没有什么不同。
为了在 header.jsp
中使用这个 user
bean,可把它从 tile 范围复制到一个其他 bean 能够理解的范围。这可以使用tiles:useAttribute
标签来实现。 tiles:useAttribute
标签类似于 jsp:useBean
操作,只不过仅适用于 tile 范围(header2.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<tiles:useAttribute id="user" name="user" classname="rickhightower.UserDomainObject"/>
因此 tiles:useAttribute
将把 user 对象从 tile 范围复制到页面范围。一旦 bean 得到定义,您就能够像使用页面范围中定义的任何 bean 一样使用它:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<bean:write name="user" property="userName"/>
接下来,让我们看一下新的 header2.jsp
文件的完整清单:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <center> <table> <tr> <tiles:useAttribute id="user" name="user" classname="rickhightower.UserDomainObject"/> <td width="33%" bgcolor="#36566E"> <div align='left'> <font size="1" color="orange"> currently logged in as <bean:write name="user" property="userName"/> </font> </div> </td> <td width="33%"> <font color="#36566E"> <tiles:getAsString name="title" ignore="true"/> </font> </td> <td width="33%" bgcolor="#36566E"> <div align='left'> <font size="1" color="white"> <blockquote> <bean:write name="user" property="firstName"/> <br /> <bean:write name="user" property="lastName"/> <br /> </blockquote> </font> </div> </td> </tr> </table> </center>
可以看到,页眉现在显示了关于当前登录站点的用户信息 ―― 这是一个强大的特性。可以创建专门用于显示域对象的 tile,然后在应用程序的许多部分重用那些 tile。考虑到这点,弄清为什么人们原先考虑将 Tiles 框架称为“组件”就很容易了:您事实上能够创建显示组件。与自定义标签(JSP 2.0 之前的版本)不同,这些组件全都是在 JSP 页面中创建的。
2. 列表
经常会遇到必须传递多个参数的情况。例如,您可能想要传递一个参数列表,以显示 tile 布局的导航区域中的链接。
回顾前面的“雇员清单”例子,您可能有一个 Web 应用程序需要显示公司的分公司、部门和雇员。当在雇员名单视图中时,employeeListing.jsp
将处于 tile 布局的内容区域,而当前分公司的部门链接将处于导航区域中。当用户单击某个部门链接时,该部门的雇员的新名单将会显示出来。当在部门视图中时, deptListing.jsp
将处于 tile 布局的内容区域,而当前公司的分公司链接列表将处于导航区域中。当用户单击某个分公司链接时,新的部门名单就会显示出来。因此,每个页面(employeeListing.jsp
和 deptListing.jsp
)都将传入一个新的链接列表。您可以使用putList
来完成这个任务。
Tile 允许用户使用 putList
子元素传入链接 ―― 适合于在 XML 和 JSP 定义中使用,或在对定义的调用或 JSP 中的 tile 布局中使用。
假设您想使用一组标准导航链接作为站点布局。可以在 tile 配置文件中使用 putList
子元素来指定这样的链接,如下所示(tiles-def.xml
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<definition name="siteLayoutDef3" path="/siteLayout3.jsp"> <put name="title" value="Rick Hightower Stock Quote System"/> <put name="header" value="/header2.jsp"/> <put name="footer" value="/footer.jsp"/> <put name="content" type="string"> Content goes here </put> <putList name="items"> <item value="Home" link="/index.html"/> <item value="Wiley" link="http://www.wiley.com"/> <item value="Trivera Technologies" link="http://www.triveratech.com/"/> <item value="Virtuas" link="http://www.virtuas.com/"/> <item value="Rick Hightower" link="http://www.rickhightower.com"/> <item value="Rick's Blog" link="http://rickhightower.blogspot.com/"/> </putList> </definition>
putList
元素允许您指定与链接相关联的项的一个列表。在上面的清单中,putList
定义了六个链接。
items
列表(java.util.List
)被放入 tile 范围。名称 items
使用 putList
元素的 name
属性来设置。
item
元素通过把 org.apache.struts.tiles.beans.MenuItem
的一个实例插入该列表来定义一个链接。value 属性对应于链接上的标签(label),而 link 则指向链接的 URL。
图标与工具提示
item
元素还有为链接指定工具提示和图标的元素。通过查看 Struts 源代码中的 DTD (tiles-config_1_1.dtd
),可以了解有关 item
元素和 putList
的更多内容。
要使用这种链接列表,必须修改 tile 布局,如下所示(siteLayout3.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> <tiles:importAttribute /> <html> <head> <logic:present name="title"> <title> <tiles:getAsString name="title" ignore="true"/> </title> </logic:present> </head> <body> <table width="500" border="0" cellspacing="0" cellpadding="0"> <tr bgcolor="#36566E"> <td height="68" width="70%"> <div align="left"> <img src="images/hp_logo_rickhightower.gif" width="220" height="74"> </div> </td> </tr> <tr> <td height="68" width="2000"> <tiles:insert attribute="header" ignore="true"> <tiles:put name="title" beanName="title" beanScope="tile"/> <tiles:put name="user" beanName="user" beanScope="session"/> </tiles:insert> </td> </tr> <table> <tr> <td width="50%"> <ul> <logic:iterate id="item" name="items" type="org.apache.struts.tiles.beans.MenuItem"> <li> <bean:define id="link" name="item" property="link" type="java.lang.String"/> <logic:match name="link" location="start" value="/"> <html:link page="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:match> <logic:notMatch name="link" location="start" value="/"> <html:link href="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:notMatch> </li> </logic:iterate> </ul> </td> <td width="50%"> <div align="center"> <tiles:insert attribute="content"/> </div> </td> </tr> </table> <tr> <td> <tiles:insert attribute="footer" ignore="true"/> </td> </tr> </table> </body> </html>
特别要注意在列表上进行迭代的代码段:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<ul> <logic:iterate id="item" name="items" type="org.apache.struts.tiles.beans.MenuItem"> <li> <bean:define id="link" name="item" property="link" type="java.lang.String"/> <logic:match name="link" location="start" value="/"> <html:link page="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:match> <logic:notMatch name="link" location="start" value="/"> <html:link href="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:notMatch> </li> </logic:iterate> </ul>
后面会对此进行简化。
tiles:importAttribute
标签将 tile 范围中的属性导入到页面范围。它类似于 tiles:useAttrribute
标签,但它更接近列枪而不是解剖刀。它是懒散的、肮脏的和便宜的;我一直用它(这说明了我什么呢?)。这有效地将条目列表从 tile 范围拷贝到页面范围。
注意: tiles:importAttribute
可拷贝到任何指定的范围。
默认情况下,tiles:importAttribute
将所有这些属性拷贝到页面范围。你也可以通过使用范围属性将这些属性拷贝到其他范围。
一旦条目列表在页面范围中,您就可以使用标准 Struts 标签访问它,如下所示(siteLayout3.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<logic:iterate id="item" name="items" type="org.apache.struts.tiles.beans.MenuItem"> ... </logic:iterate>
注意使用 logic
标签实现的逻辑用于显示链接。可以检查链接是否以“/”开始,从而确定链接是否是相对的。如果链接 是 相对的,使用 html:link
标签的 page
属性。否则,如果链接指向绝对 URL 的话,使用 html:link
标签的 href
属性,如下所示(siteLayout3.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<bean:define id="link" name="item" property="link" type="java.lang.String"/> <logic:match name="link" location="start" value="/"> <html:link page="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:match> <logic:notMatch name="link" location="start" value="/"> <html:link href="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:notMatch>
如您所想到的那样,您可能想要使用这一显示逻辑来在不止一个位置显示菜单项。这就是说,您可能想要在这个页面的范围之外重用它。在稍后部分,您将看到如何通过将一个 tile 布局嵌套进另一个 tile 布局来实现这一点。
除了能向 tile 定义中的列表添加条目之外,还可以使用 tiles:putList
元素和它的 tiles:add
子元素向 JSP 中的列表添加条目(index6.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <%@ page import="org.apache.struts.tiles.beans.SimpleMenuItem"%> <tiles:insert definition="siteLayoutDef4"> <tiles:put name="title" type="string" value="Get Rick Hightower Stock Quote6"/> <tiles:put name="content" value="indexContent5.jsp"/> <tiles:putList name="items"> <jsp:useBean id="item1" class="SimpleMenuItem"/> <jsp:setProperty name="item1" property="link" value="/index.html"/> <jsp:setProperty name="item1" property="value" value="Home"/> <tiles:add beanName="item1"/> </tiles:putList> </tiles:insert>
上面的清单使用 jsp:useBean
来创建 SimpleMenuItem
的实例。然后使用 jsp:setProperty
来设置 SimpleMenuItem
bean 的链接和值属性。最后,使用 tiles:add
将这个 bean 添加到列表中。
在上面的例子中,添加了一个 SimpleMenuItem
,它细分了 tile 布局使用的 MenuItem
。然而,您可以添加任何 bean 类型。
注意: 在 XML 中添加任何类型的 bean。
要在 tiles XML 定义中添加任何类型的 bean,可使用 putList
的子元素 bean。这个 bean 元素带有一个 id
和classtype
。对于简单类型,您也可以使用 putList
的 add
子元素。请参阅 tiles configuration DTD (tiles-config_1_1.dtd
) 以获取更多信息。
1. 高级定义概念
几个 JSP 页面经常使用相同的默认参数。其他页面也使用相同的 tile 布局但使用不同的 tile 参数。无需再定义一个完全不同的定义,一个定义可以扩展另一个定义。extends
属性让一个定义扩展另一个定义。
下面是一个例子:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<definition name="siteLayoutDef3" path="/siteLayout3.jsp"> <put name="title" value="Rick Hightower Stock Quote System"/> <put name="header" value="/header2.jsp"/> <put name="footer" value="/footer.jsp"/> <put name="content" type="string"> Content goes here </put> <putList name="items"> <item value="Home" link="/index.html"/> <item value="Wiley" link="http://www.wiley.com"/> <item value="Trivera Technologies" link="http://www.triveratech.com/"/> <item value="Virtuas" link="http://www.virtuas.com/"/> <item value="Rick Hightower" link="http://www.rickhightower.com"/> <item value="Rick's Blog" link="http://rickhightower.blogspot.com/"/> </putList> </definition> <definition name="siteLayoutDef4" extends="siteLayoutDef3"> <put name="title" value="Rick Hightower Quote Sub System"/> <putList name="items"> <item value="Home" link="/index.html"/> <item value="Wiley" link="http://www.wiley.com"/> <item value="Trivera Technologies" link="http://www.triveratech.com/"/> <item value="Virtuas" link="http://www.virtuas.com/"/> </putList> </definition> <definition name="siteLayoutDef5" extends="siteLayoutDef4"> <putList name="items"> </putList> </definition> <definition name="siteLayoutDef6" path="/siteLayout4.jsp" extends="siteLayoutDef4"> </definition>
注意 siteLayoutDef4
扩展了 siteLayoutDef3
,覆盖了标题值,并定义了一个更短的导航列表。它从所覆盖的siteLayoutDef4
继承了所有其他参数,即页眉、页脚和内容。此外,注意 siteLayoutDef5
也扩展了 siteLayout4
,只是它清空了条目列表。一个定义继承了它的上层定义及更上一层定义(依此类推无限制)的所有属性。
除了覆盖属性之外,还可改变 tile 布局 JSP。看看 siteLayoutDef6
,它扩展自 siteLayoutDef5
,并指定了一个新的 tile 布局(siteLayout4.jsp
)。
一个 tile 布局可以插入到另一个 tile 布局中,依此类推。实际上,创建的 tile 布局如此之小,以至于它们本身并不是真正的模板。相反,它们是更类似于自定义标签的小型可视组件,而不是页面模板。
记住您实现的逻辑用于显示一条链接。可以检查链接是否以“/”开始,从而确定链接是否是相对的,然后再正确地显示它。如果想要在应用程序的多个地方使用同一例程,需要创建一个可视组件。
可视组件只是另一种 tile 布局。tile 布局是一个可视组件还是一个模板,只取决于您的观点(旁观者清)。下面的 tile 布局定义了一个可视组件,用于显示一个链接(linkLayout.jsp
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> <tiles:useAttribute id="item" name="item" classname="org.apache.struts.tiles.beans.MenuItem"/> <bean:define id="link" name="item" property="link" type="java.lang.String"/> <logic:match name="link" location="start" value="/"> <html:link page="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:match> <logic:notMatch name="link" location="start" value="/"> <html:link href="<%=link%>"> <bean:write name="item" property="value"/> </html:link> </logic:notMatch>
这种方法与 JSP Custom 标签相比,允许您使用其他自定义标签。另外,与 Java 类(比如 Custom 标签)相比,它是一个以文档为中心的 JSP,这使得使用 HTML 标签和自定义标签更容易。
注意: JSP 2.0 标签文件。
您可能认识到 JSP 2.0 及其后续版本中 JSP 标签文件的 tile 布局的许多优点。如果您使用的 JSP 版本太老,不支持标签文件,那么您现在就可以使用这种技术。然而,如您很快就要看到的那样,按我的观点,Tiles 框架更好地实现了控制器与视图的分离。
一旦定义了可视组件,就应该为它创建一个定义,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<definition name="linkLayoutDef" path="/linkLayout.jsp"> </definition>
现在您已经定义好这个定义,通过使用 tiles:insert
标签 ,您可以在任何页面使用这个可视组件。甚至可以在另一个 tile 中使用这个可视组件。下面的代码示例展示了在前面定义的 tile 布局中使用这个可视组件 (siteLayout4.jsp
)。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<td width="50%"> <ul> <logic:iterate id="item" name="items" type="org.apache.struts.tiles.beans.MenuItem"> <li> <tiles:insert definition="linkLayoutDef"> <tiles:put name="item" beanName="item" beanScope="page"/> </tiles:insert> </li> </logic:iterate> </ul> </td>
上面的代码在条目列表上进行迭代,然后调用 tiles:insert
,将当前条目传递给可视组件 (linkLayoutDef
)以用于显示。可视组件知道如何显示一个域对象(一个菜单项)。如果您觉得自己需要再三重复编写相同的 JSP 代码,就应该考虑使用 tile 布局编写一个可视组件了。
上面的例子显式地调用定义好的可视组件。如果您使用的 tile 布局根据几个因素而变化该怎么办呢?(即这个用户是否登录,他是否处于某个特定的角色,您位于站点的哪个部分)。在这种情况下,将 tile 作为一个参数传递将是个好主意。
使用 put
元素可以完成这件事,如下所示(tiles-def.xml
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<definition name="link.layout.def" path="/linkLayout.jsp"> </definition> <definition name="siteLayoutDef7" path="/siteLayout5.jsp" extends="siteLayoutDef4"> <put name="title" value="Rick Hightower Quote System 9"/> <putList name="items"> </putList> <put name="linkDisplay" value="link.layout.def"/> </definition>
注意 siteLayoutDef7
的 linkDisplay
属性的值等于 link.layout.def
。现在在 tile 布局(siteLayout5.jsp
)的内部,您可以指定 linkDisplay
属性,而不是明确地调用一个特殊的 tile 布局定义:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<ul> <logic:iterate id="item" name="items" type="org.apache.struts.tiles.beans.MenuItem"> <li> <tiles:insert attribute="linkDisplay"> <tiles:put name="item" beanName="item" beanScope="page"/> </tiles:insert> </li> </logic:iterate> </ul>
这样的话,您的站点布局不知道它所使用的是哪种可视组件。通过切换站点布局使用哪一种可视组件,您可以通过编程切换布局部分的显示方式。
如果您觉得需要向 tile 布局中放入太多的 Java 代码,或者必须在每个指向使用特定 tile 布局的页面的操作中放相同的 Java 代码,那么应该使用 tile 控制器。在使用 controllerClass
属性插入 tile 之前,您可以指定一个进行调用的控制器类:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles"%> <tiles:insert definition="siteLayoutDef5" controllerClass="rickhightower.SimpleController"> <tiles:put name="content" value="indexContent5.jsp"/> </tiles:insert>
控制器类类似于一个操作。在控制器中,可以将模型对象映射到某个范围中,以便 tile 能够显示条目。
要编写一个 tile 控制器,必须执行以下操作:
- 创建一个实现 org.apache.struts.tiles.Controller的类。
- 实现 perform()方法。
- 在
perform()
方法中,执行一些模型操作,并将结果映射到范围中,这样 tile 就能使用它。
下面的清单展示了实现一个控制器的方法(rickhightower.SimpleController
):
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
package rickhightower;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.tiles.ComponentContext;
import org.apache.struts.tiles.Controller;
import org.apache.struts.tiles.beans.MenuItem;
import org.apache.struts.tiles.beans.SimpleMenuItem;
import java.util.ArrayList;
import java.util.List;
/**
* @author rhightower
*/
public class SimpleController implements Controller{
private MenuItem createMenuItem(String label, String link){
SimpleMenuItem item = new SimpleMenuItem();
item.setLink(link);
item.setValue(label);
return item;
}
private List getLinks(){
List list = new ArrayList();
list.add(createMenuItem("Home", "/index.html"));
list.add(createMenuItem("Rick's", "http://www.rickhightower.com"));
list.add(createMenuItem("Trivera", "http://www.triveratech.com"));
return list;
}
/* (non-Javadoc)
*
*/
public void perform(ComponentContext context,
HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext)
throws ServletException,package rickhightower;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.tiles.ComponentContext;
import org.apache.struts.tiles.Controller;
import org.apache.struts.tiles.beans.MenuItem;
import org.apache.struts.tiles.beans.SimpleMenuItem;
import java.util.ArrayList;
import java.util.List;
/**
* @author rhightower
*/
public class SimpleController implements Controller{
private MenuItem createMenuItem(String label, String link){
SimpleMenuItem item = new SimpleMenuItem();
item.setLink(link);
item.setValue(label);
return item;
}
private List getLinks(){
List list = new ArrayList();
list.add(createMenuItem("Home",
"/index.html"));
list.add(createMenuItem("Rick's",
"http://www.rickhightower.com"));
list.add(createMenuItem("Trivera",
"http://www.triveratech.com"));
return list;
}
/* (non-Javadoc)
*
*/
public void perform(ComponentContext context,
HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext)
throws ServletException, IOException {
List items = (List)getLinks();
context.putAttribute("items",items);
}
IOException {
List items = (List)getLinks();
context.putAttribute("items",items);
}
}
注意 perform()
方法获得传递过来的组件上下文。组件上下文带有 tile 范围的属性。将东西放进组件上下文中可将它们放进 tile 范围中。在这个简单的例子中,调用 getLinks
,它返回一个简单的映射到 tile 范围的 MenuItems
列表。一个真实的例子很可能会涉及到模型――也许是一个面(facade),它与数据库进行通信,查找特定于登录进系统的用户类型的链接。
注意:使用操作作为控制器。
您也可以使用操作作为 tile 的控制器。要完成这项任务,请指定带有 controllerUrl
属性的操作的路径。
您可能还未觉察到,在您安装 Tiles 插件时,它安装了一个自定义请求处理程序,扩展了 Struts 处理 ActionForward
的方式。因此,您应该转到 tile 定义而不是 JSP 页面。
假设您有一个定义类似这样:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<definition name="main.index" extends="siteLayoutDef7"> <put name="content" value="/indexContent.jsp"/> </definition>
在您的 struts 配置文件中,您可以定义一个 forward 以转到 main.index
定义,而不是指定一个 JSP 页面:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
<action path="/Lookup" type="rickhightower.SimpleLookupAction" name="lookupForm" input="/index.jsp"> <forward name="success" path="/quote.jsp"/> <!-- forward name="failure" path="/index.jsp"/ --> <forward name="failure" path="main.index"/> </action>
可以转到定义已证实是一项强大的工具,消除了 JSP 中无关的逻辑。例如,如果用户以经理身份而不是以常规用户身份登录,您可以将该用户转到一个定义,它定义了只有经理才能使用的特殊参数 tiles。
经理的定义可以在常规用户定义的基础上进行扩展。如果 tile 布局使用带有 ignore
属性的 insert
标签的话,它们甚至可以使用相同的 tile 布局。这个操作将选择正确的 forward。您根本无需使用 logic:*
标签。
将逻辑从 JSP 中取出并置入控制器中,是正确方向的一步,并且使用 Tiles 框架来执行这一步是如此地容易。
2. 结束语
如果您是 Tiles 框架的初学者,并且已经阅读了本教程,那么您已经迈出了重要一步。在相对短的时间中,我们介绍了:
- Tiles 框架和架构。
- 如何构建和使用 tile 布局作为站点模板。
- 如何在 XML 和 JSP 中使用 tile 定义。
- 如何在 tile 范围中移出和移入对象。
- 如何使用属性列表。
- 如何嵌套 tiles。
- 如何构建和使用 tile 布局作为小型可视组件。
- 如何细分定义。
- 如何创建 tile 的控制器。
- 如何使用 tile 作为一个
ActionForward
。
Tiles 框架使得创建可重用页面和可视组件更加容易。通过组装可重用 tiles,开发人员能够构建 Web 应用程序。可以使用 tiles 作为模板或者可视组件。
在某些方面,tile 布局更类似于一个显示函数。首先您传递需要使用的 tile 布局参数。参数可以是简单的字符串、bean 或者 tiles。参数是 tile 的属性,存储在 tile 的 tile 范围中。对于它的一部分,tile 范围类似于页面范围,比请求范围更少见。tile 范围允许 tile 的用户传递参数(也称为属性)给 tile。
定义允许您定义 tiles 的默认参数。定义能够在 JSP 或者 XML 中进行定义。定义能够扩展其他定义,这类似于类可以扩展另一个类。此外,定义可以覆盖它所扩展的定义的一部分。
Tiles 框架包括了它自己的 RequestProcessor
,以便作为 ActionForward
处理 tile 布局。因此如果您安装了 Tiles 插件的话,可以转到 tile 定义而不是 JSP 。
如果您正在使用 Struts 而不是 Tiles,那么您不能从 Struts 获得完全受益,并且很可能进行不必要的自我重复。Tiles 框架使得创建可重用的站点布局和可视组件变得切实可行。
- 下载本教程使用的源代码。有两个版本可用:一个带有 jar 文件 ,一个不带 jar 文件。
- 如果您想要使用 Tiles 和 Struts,请下载 Struts 1.1,它包括了一个 Tiles 框架。
- 如果想要单独使用 Tiles ,请访问 Tiles 网站,下载该软件或者获取其他有用信息。
- 在 官方 Tomcat 网站下载最新版的 Apache Tomcat 。
- Tomcat 5 有一些有用的附件。Sing Li 的文章详细介绍了它们,servlet filtering(developerWorks,2003 年 3 月)
- developerWorks上还有以下关于 Struts/Tiles 的文章:
- Malcolm Davis 的“Struts, an open-source MVC implementation”(2001 年 2 月),介绍了 Struts 并展示了如何管理大型 Web 站点的复杂性。
- Wellie Chao 的“Struts and Tiles aid component-based development”(2002 年 6 月),结合使用 Struts 和 Tiles 来构建 Web 应用程序。
- 在“Struttin' your stuff with WebSphere Studio Application Developer, Part 2: Tiles”中(2002 年 11 月),David Carew 展示了如何使用带有 Struts 的 Tiles 模板化框架。
- “集成 Struts、Tiles 和 JavaServer Faces” (2003 年 10 月)将三种技术的强大功能结合到一个功能强大的软件包中。
- “为 Web 服务构建 Struts 应用程序”(2003 年 12 月)将 MVC 的强大功能带进 Web 服务中。
- David Carew 的“Go-ForIt Chronicles, Part 19: Struttin' your stuff with WebSphere Studio” (2002 年 9 月),该教程将带您使用 WebSphere Studio IDE 开始构建基于 Struts 的应用程序。
- Rick Hightower 还与 James Goodwill 合著了 Mastering Jakarta Struts, 2nd edition 一书(Wrox Press)。
- Chuck Cavaness 是Programming Jakarta Struts 一书的作者,从他的书中摘录了本系列 关于 Tiles 的四部分系列文章 。
- JavaWorld 上 Prakash Malani 的“UI design with Struts and Tiles”提供了 Tiles 的介绍。
- 在 IBM developerWorks 的 Java 技术专区上可以找到数百篇关于 Java 编程的各个方面的文章。
Rick Hightower喜欢使用 Java 技术、Ant、Struts、IBM Emerging Technologies Toolkit(ETTK)以及 XDoclet。 Rick 最近担任ArcMind Inc.的 CTO,这是一家主要从事企业开发方面的顾问、咨询和培训服务的公司。作为 IBMdeveloperWorks 的积极投稿者,Rick 已编写了 10 多个教程,涵盖从 EJB (Enterprise JavaBeans) 技术到 Web 服务以至 XDoclet 的广泛内容。
在 eBlox 工作的同时,Rick 和 eBlox 团队使用 Struts 构建了用于在线电子商店的两个框架和一个 ASP(application service provider,应用服务提供程序)。 他们远在 1.0 版发布之前就在使用 Struts。 Rick 最近帮助 Trivera Technologies 整理了一套很受欢迎的课程,这些课程讲授运行在 Tomcat、Resin EE、WebSphere Studio Application Developer 以及其他平台上的 Structs。
Rick 与 James Goodwill 合著了Mastering Struts, 2nd edition 一书(Wrox Press 出版)。他还参与编写了 Java Tools for Extreme Programming 一书(John Wiley& Sons 出版,2001 年),这本书于 2002 年在 Amazon.com 网站上连续三个月被评为“畅销软件开发书籍”。它介绍了如何把 Ant、JUnit、Cactus 等应用于 J2EE(Java 2 Platform, Enterprise Edition)开发。 Rick 还为Mastering Tomcat Development 一书(John Wiley & Sons,2002 年)撰写了两章的内容,并参与编写过其他许多出版物。
Rick 在 2003 JavaOne 开发人员大会上作了关于 EJB CMP/CMR 和 XDoclet 的演讲,在 TheServerSide.com 软件座谈会上作了关于使用 XDoclet 进行 J2EE 开发的演讲。此外,Rick 还在 JDJEdge 和 WebServicesEdge 大会上作过演讲。而且,Rick 还在 Complete Programmer Network 座谈会(跨越美国 6 个不同城市)上作了关于高级 Struts 主题的演讲。
当不在全国各地讲授 Trivera Struts 课程、在大会上作关于 Struts 的演讲,或从事 Struts 咨询的时候,Rick 喜欢在通宵咖啡馆喝咖啡,编写关于 Struts 和其他主题的文章,以及以第三人称写关于他自己的事情。
注: 以上四篇文章来自http://www.ibm.com/developerworks/cn