在本系列的第一篇文章中,您首先了解了JSTL。 我们描述了使用其表达语言 (EL)来访问数据并对其进行操作。 如您所知,EL用于为JSTL定制标记的属性分配动态值,因此与JSP表达式具有相同的作用,用于为内置操作和其他定制标记库指定请求时间属性值。
为了演示EL的用法,我们从core
库中引入了三个标签: <c:set>
, <c:remove>
和<c:out>
。 <c:set>
和<c:remove>
用于管理范围变量; <c:out>
用于显示数据,尤其是使用EL计算的值。 然后,基于此基础工作,我们将在本文中将注意力集中在core
库中的其余标记上,这些标记可大致分为两大类:流控制和URL管理。
应用范例
为了演示JSTL标签,我们将使用一个工作应用程序中的示例来阅读本系列的其余文章。 由于它们越来越受欢迎和熟悉,我们将为此目的使用基于Java的简单Weblog。 请参阅参考资料,以下载此应用程序的JSP页面和源代码。 Weblog(也称为博客)是基于Web的期刊,对Weblog作者感兴趣的主题进行了简短评论,通常带有指向相关文章或Web上其他地方的讨论的链接。 正在运行的应用程序的屏幕截图如图1所示。
图1. Weblog应用程序
尽管完整的实现需要几个Java类,但是在表示层中仅引用了Weblog应用程序的两个类Entry
和UserBean
。 因此,要理解JSTL示例,只有这两个类很重要。 Entry
和UserBean
类图如图2所示。
图2. Weblog应用程序的类图
Entry
类表示Weblog中的带日期的条目。 它的id
属性用于在数据库中存储和检索条目,而title
和text
属性代表条目的实际内容。 created
和lastModified
属性引用了Java语言的Date
类的两个实例,它们表示条目是何时首次创建和最后编辑的。 author
属性引用一个UserBean
实例,该实例标识了创建条目的人。
UserBean
类存储有关应用程序经过身份验证的用户的信息,例如用户名,全名和电子邮件地址。 此类还包括用于与关联数据库进行交互的id
属性。 它的最终属性roles
引用了一个String
值列表,这些值标识与相应用户关联的应用程序特定角色。 对于Weblog应用程序,相关的角色是“用户”(所有应用程序用户共有的默认角色)和“作者”(指定允许创建和编辑Weblog条目的用户的角色)。
流量控制
因为可以使用EL代替JSP表达式来指定动态属性值,所以它减少了页面作者使用脚本元素的需要。 由于脚本元素可能是JSP页面中维护问题的重要来源,因此提供简单(和标准)的替代选择是JSTL的主要优势。
EL从JSP容器检索数据,遍历对象层次结构,并对结果执行简单的操作。 但是,除了访问和处理数据外,JSP脚本元素的另一种常见用法是流控制。 特别是,页面作者通常使用scriptlet来实现迭代或有条件的内容,这是相当普遍的。 但是,由于此类操作超出了EL的功能,因此core
库提供了一些自定义操作,以迭代 , 条件化和异常处理的形式管理流控制。
迭代
在Web应用程序的上下文中,迭代主要用于获取和显示数据集合,通常以列表或表中行的形式显示。 实现迭代内容的主要JSTL操作是<c:forEach>
定制标记。 该标签支持两种不同的迭代样式:在整数范围内进行迭代(例如Java语言的for
语句)和在集合上进行迭代(例如Java语言的Iterator
和Enumeration
类)。
为了遍历整数范围,使用了清单1中所示的<c:forEach>
标记的语法。 begin
和end
属性应为静态整数值或为整数值的表达式。 它们分别指定了迭代索引的初始值和应该停止迭代的索引值。 使用<c:forEach>
遍历整数范围时,这两个属性是必需的,其他所有属性都是可选的。
清单1.通过<c:forEach>操作进行数值迭代的语法
<c:forEach var="name" varStatus="name"
begin="expression" end="expression" step="expression">
body content
</c:forEach>
step
属性(如果存在)还必须具有整数值。 它指定每次迭代后要添加到索引的数量。 因此,迭代的索引从begin
属性的值begin
,由step
属性的值递增,并在超过end
属性的值时停止迭代。 请注意,如果省略step
属性,则步长默认为1。
如果指定了var
属性,则将创建具有指定名称的范围变量,并为每次迭代进行分配索引的当前值。 此范围变量具有嵌套的可见性-只能在<c:forEach>
标记的正文中进行访问。 (稍后将讨论可选的varStatus
属性的用法。)清单2显示了<c:forEach>
操作的示例,该操作用于迭代一组固定的整数值。
清单2.使用<c:forEach>标记来生成与一系列数值对应的表格数据
<table>
<tr><th>Value</th>
<th>Square</th></tr>
<c:forEach var="x" begin="0" end="10" step="2">
<tr><td><c:out value="${x}"/></td>
<td><c:out value="${x * x}"/></td></tr>
</c:forEach>
</table>
此示例代码生成一个表,列出前五个偶数的平方,如图3所示。这是通过为begin
和step
属性都指定值为2,为end
属性指定值为10来实现的。 此外, var
属性用于创建用于存储索引值的作用域变量,该变量在<c:forEach>
标记的正文中被引用。 具体来说,一对<c:out>
动作用于显示索引及其平方,后者使用一个简单的表达式进行计算。
图3.清单2的输出
遍历集合的成员时,使用<c:forEach>
标记的另一个属性: items
属性,如清单3所示。当使用这种形式的<c:forEach>
标记时, items
属性是唯一必需的属性。 items
属性的值应该是将在其成员上进行迭代的集合,并且通常使用EL表达式指定。 如果通过<c:forEach>
标记的item
属性指定了变量名,则对于每次迭代,命名变量都将绑定到集合的连续元素。
清单3.通过<c:forEach>操作遍历集合的语法
<c:forEach var="name" items="expression" varStatus="name"
begin="expression" end="expression" step="expression">
body content
</c:forEach>
<c:forEach>
标记支持Java平台提供的所有标准集合类型。 另外,您可以使用此操作来遍历数组的元素,包括基本数组。 表1包含了items
属性支持的值的完整列表。 如表的最后一行所示,JSTL定义了自己的接口javax.servlet.jsp.jstl.sql.Result
,用于遍历SQL查询的结果。 (我们将在本系列的后续文章中提供有关此功能的更多详细信息。)
表1. <c:forEach>标记的items
属性支持的集合
items 价值 | 结果item 值 |
---|---|
java.util.Collection | 从调用iterator() 元素 |
java.util.Map | java.util.Map.Entry 实例 |
java.util.Iterator | 迭代器元素 |
java.util.Enumeration | 枚举元素 |
Object 实例数组 | 数组元素 |
基本值数组 | 包装的数组元素 |
逗号分隔的String | 子串 |
javax.servlet.jsp.jstl.sql.Result | SQL查询中的行 |
您可以使用begin
, end
和step
属性来限制集合中的哪些元素包含在迭代中。 与通过<c:forEach>
进行数字迭代的情况一样,当迭代集合的元素时,也会维护迭代索引。 实际上,只有与与指定的begin
, end
和step
值匹配的索引值相对应的那些元素才会由<c:forEach>
标记实际处理。
清单4显示了<c:forEach>
标记,用于遍历一个集合。 对于此JSP片段,已将一个名为entryList
的作用域变量设置为Entry
对象的列表(特别是ArrayList
)。 <c:forEach>
标记依次处理此列表的每个元素,将其分配给名为blogEntry
的作用域变量,并生成两个表行-一个用于Weblog条目的title
,另一个用于其text
。 这些属性是通过带有相应EL表达式的一对<c:out>
动作从blogEntry
变量中检索的。 请注意,由于Weblog条目的标题和文本都可能包含HTML标记,因此两个<c:out>
标记的escapeXml
属性都设置为false。 图4显示了结果。
清单4.使用<c:forEach>标记显示给定日期的Weblog条目
<table>
<c:forEach items="${entryList}" var="blogEntry">
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图4.清单4的输出
其余的<c:forEach>
属性varStatus
不管迭代整数还是集合都起着相同的作用。 像var
属性一样, varStatus
用于创建范围变量。 但是,不是存储当前索引值或当前元素,而是将varStatus
属性命名的变量分配给javax.servlet.jsp.jstl.core.LoopTagStatus
类的实例。 此类定义了一组表2中列出的属性,这些属性描述了迭代的当前状态。
表2. LoopTagStatus对象的属性
属性 | 吸气剂 | 描述 |
---|---|---|
当前 | getCurrent() | 当前迭代的项目(来自集合) |
指数 | getIndex() | 当前迭代的从零开始的索引 |
计数 | getCount() | 当前迭代的基于一的计数 |
第一 | isFirst() | 指示当前回合是否是迭代的第一遍的标志 |
持续 | isLast() | 指示当前回合是否是迭代的最后一次传递的标志 |
开始 | getBegin() | begin 属性的值 |
结束 | getEnd() | end 属性的值 |
步 | getStep() | step 属性的值 |
清单5显示了如何使用varStatus
属性的示例。 它修改了清单4中的代码,以将Weblog条目的编号添加到显示其标题的表行中。 通过为varStatus
属性指定一个值,然后访问所得范围变量的count
属性来完成此操作。 结果如图5所示。
清单5.使用varStatus属性显示Weblog条目的数量
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<tr><td align="left" class="blogTitle">
<c:out value="${status.count}"/>.
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图5.清单5的输出
除了<c:forEach>
, core
库还提供了第二个迭代标签: <c:forTokens>
。 此自定义操作是Java语言的StringTokenizer
类的JSTL类似物。 清单6中显示的<c:forTokens>
标记与<c:forEach>
的面向集合的版本具有相同的属性集,并带有一个附加属性。 对于<c:forTokens>
,要通过items
属性指定要标记化的字符串,而通过delims
属性提供用于生成标记的定界符集。 与<c:forEach>
,您可以使用begin
, end
和step
属性将要处理的标记限制为与相应索引值匹配的标记。
清单6.使用<c:forTokens>操作迭代字符串的令牌的语法
<c:forTokens var="name" items="expression"
delims="expression" varStatus="name"
begin="expression" end="expression" step="expression">
body content
</c:forTokens>
条件化
对于包含动态内容的网页,您可能希望不同类别的用户查看不同形式的内容。 例如,在我们的Weblog中,访问者应该能够阅读条目并可能提交反馈,但是只有授权用户才能发布新条目或编辑现有内容。
通过在同一JSP页面中实现这些功能,然后使用条件逻辑来控制按请求显示的内容,通常可以提高可用性和软件维护。 core
库提供了两个不同的条件标记- <c:if>
和<c:choose>
-以实现这些功能。
这两个动作中最直接的<c:if>
仅评估一个测试表达式,然后仅在该表达式的表达式为true
时才处理其主体内容。 如果不是,则标签的正文内容将被忽略。 如清单7所示, <c:if>
可以选择通过其var
和scope
属性将测试结果分配给作用域变量(在这里,它们与<c:set>
作用相同)。 如果测试成本很高,则此功能特别有用:可以将结果缓存在作用域变量中,并在随后对<c:if>
或其他JSTL标签的调用中进行检索。
清单7. <c:if>条件操作的语法
<c:if test="expression" var="name" scope="scope">
body content
</c:if>
清单8显示了<c:if>
与<c:forEach>
标记的LoopTagStatus
对象的first
属性<c:forEach>
。 在这种情况下,如图6所示,一组Weblog条目的创建日期显示在该日期的第一个条目的正上方,但在其他任何条目之前都不会重复。
清单8.使用<c:if>显示Weblog条目的日期
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<c:out value="${blogEntry.created}"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
图6.清单8的输出
如清单8所示, <c:if>
标记为条件化内容的简单情况提供了非常紧凑的表示法。 对于需要互斥测试以确定应显示哪些内容的情况,JSTL core
库还提供了<c:choose>
操作。 清单9显示了<c:choose>
的语法。
清单9. <c:choose>操作的语法
<c:choose>
<c:when test="expression">
body content
</c:when>
...
<c:otherwise>
body content
</c:otherwise>
</c:choose>
每个要测试的条件都由相应的<c:when>
标签表示,其中必须至少有一个。 仅处理test
为true
的第一个<c:when>
标记的主体内容。 如果<c:when>
测试均未返回true
,则将处理<c:otherwise>
标记的正文内容。 但是请注意, <c:otherwise>
标记是可选的。 一个<c:choose>
标记最多可以有一个嵌套的<c:otherwise>
标记。 如果所有<c:when>
测试都为false
并且没有任何<c:otherwise>
操作,则不会处理任何<c:choose>
正文内容。
清单10显示了<c:choose>
标记的示例。 此处,协议信息是从请求对象中检索的(通过EL的pageContext
隐式对象),并使用简单的字符串比较进行测试。 根据这些测试的结果,显示相应的文本消息。
清单10.使用<c:choose>进行内容条件化
<c:choose>
<c:when test="${pageContext.request.scheme eq 'http'}">
This is an insecure Web session.
</c:when>
<c:when test="${pageContext.request.scheme eq 'https'}">
This is a secure Web session.
</c:when>
<c:otherwise>
You are using an unrecognized Web protocol. How did this happen?!
</c:otherwise>
</c:choose>
异常处理
最终的流控制标记是<c:catch>
,它允许在JSP页面内进行基本的异常处理。 更具体地说,将捕获并忽略在此标记的主体内容内引发的任何异常(即,将不会调用标准JSP错误处理机制)。 但是,如果引发异常并且已指定<c:catch>
标记的可选var
属性,则会将该异常分配给指定的变量(具有页面范围),从而在页面本身内启用自定义错误处理。 清单11显示了<c:catch>
的语法( 清单18中稍后将显示一个示例)。
清单11. <c:catch>操作的语法
<c:catch var="name">
body content
</c:catch>
URL动作
JSTL core
库中的其余标签集中于URL。 其中第一个,恰当地命名为<c:url>
标记,用于生成URL。 特别是, <c:url>
提供了三个功能要素,这些要素在为J2EE Web应用程序构造URL时特别重要:
- 在当前Servlet上下文的名称之前
- URL重写以进行会话管理
- 请求参数名称和值的URL编码
清单12显示了<c:url>
标记的语法。 value
属性用于指定基本URL,然后标记会根据需要对其进行转换。 如果此基本URL以正斜杠开头,则将在servlet上下文名称前加上前缀。 可以使用context
属性提供显式上下文名称。 如果省略此属性,那么将使用当前Servlet上下文的名称。 这特别有用,因为servlet上下文名称是在部署期间而不是在开发期间确定的。 (如果基本URL不是以正斜杠开头,则假定它是相对URL,在这种情况下,不需要添加上下文名称。)
清单12. <c:url>操作的语法
<c:url value="expression" context="expression"
var="name" scope="scope">
<c:param name="expression" value="expression"/>
...
</c:url>
URL重写由<c:url>
操作自动执行。 如果JSP容器检测到存储用户当前会话ID的cookie,则无需重写。 但是,如果不存在这样的cookie,则将重写<c:url>
生成的所有URL,以对会话ID进行编码。 请注意,如果后续请求中存在适当的cookie,则<c:url>
将停止重写URL以包括该ID。
如果为var
属性提供了一个值(可选,并为scope
属性提供了相应的值),则将生成的URL分配为指定范围变量的值。 否则,将使用当前的JspWriter
输出结果URL。 这种直接输出结果的功能使<c:url>
标记可以显示为例如HTML <a>
标记的href
属性的值,如清单13所示。
清单13.生成一个URL作为HTML标签的属性值
<a href="<c:url value='/content/sitemap.jsp'/>">View sitemap</a>
最后,如果通过嵌套的<c:param>
标记指定了任何请求参数,则它们的名称和值将使用HTTP GET请求的标准符号附加到生成的URL。 此外,还会执行URL编码:这些参数的名称或值中存在的任何必须转换以产生有效URL的字符都将被正确转换。 清单14说明了<c:url>
的行为。
清单14.生成带有请求参数的URL
<c:url value="/content/search.jsp">
<c:param name="keyword" value="${searchTerm}"/>
<c:param name="month" value="02/2003"/>
</c:url>
清单14中的JSP代码已部署到名为blog
的servlet上下文中,范围变量searchTerm
的值已设置为"core library"
。 如果检测到会话cookie,则清单14生成的URL将类似于清单15中的URL。请注意,上下文名称已被添加,请求参数已被添加。 此外, keyword
参数值中的空格和month
参数值中的正斜杠已按照HTTP GET参数的要求进行了编码(具体来说,该空格已转换为+
,斜杠已转换为序列%2F
)。
清单15.在存在会话cookie的情况下生成的URL
/blog/content/search.jsp?keyword=foo+bar&month=02%2F2003
当不存在会话cookie时,清单16中的URL是结果。 同样,该Servlet上下文已被添加,并且URL编码的请求参数已被添加。 但是,此外,基本URL已被重写为包括会话ID的规范。 当浏览器发送对以这种方式重写的URL的请求时,JSP容器会自动提取会话ID,并将该请求与相应的会话相关联。 通过这种方式,需要会话管理的J2EE应用程序不需要依靠应用程序用户启用的cookie。
清单16.在没有会话cookie的情况下生成的URL
/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
?keyword=foo+bar&month=02%2F2003
汇入内容
JSP具有两种内置机制,可将来自不同URL的内容合并到JSP页面中: include
伪指令和<jsp:include>
操作。 但是,在两种情况下,要包含的内容都必须与页面本身属于同一Web应用程序(或Servlet上下文)。 这两个标记之间的主要区别是include
伪指令在页面编译期间合并了包含的内容,而<jsp:include>
操作在JSP页面的请求时处理期间运行。
core
库的<c:import>
操作本质上是<jsp:include>
的更通用,更强大的版本(类固醇上是<jsp:include>
的一种)。 与<jsp:include>
, <c:import>
是请求时操作,其基本任务是将其他Web资源的内容插入JSP页面。 其语法与<c:url>
语法非常相似,如清单17所示。
清单17. <c:import>操作的语法
<c:import url="expression" context="expression"
charEncoding="expression" var="name" scope="scope">
<c:param name="expression" value="expression"/>
...
</c:import>
通过url
属性( <c:import>
的唯一必需属性)指定要导入的内容的URL。 允许使用相对URL,并相对于当前页面的URL进行解析。 但是,如果url
属性的值以正斜杠开头,则它将被解释为本地JSP容器内的绝对URL。 如果没有context
属性的值,则假定这样的绝对URL引用当前servlet上下文中的资源。 如果通过context
属性指定了显式上下文,则将根据命名的servlet上下文解析绝对(本地)URL。
但是, <c:import>
操作不限于访问本地内容。 完整的URI(包括协议和主机名)也可以指定为url
属性的值。 实际上,该协议甚至不限于HTTP。 <c:import>
的url
属性的值中可以使用java.net.URL
类支持的任何协议。 清单18显示了此功能。
在这里, <c:import>
操作用于包含通过FTP协议访问的文档的内容。 另外, <c:catch>
操作用于本地处理FTP文件传输期间可能发生的任何错误。 这是通过使用<c:catch>
的var
属性为异常指定范围变量,然后使用<c:if>
检查其值来实现的。 如果引发异常,则会发生对范围变量的赋值:如清单18中的EL表达式所示,其值不会为空。 由于FTP文档的检索将失败,因此将显示一条错误消息。
清单18.组合<c:import>和<c:catch>的示例
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
Sorry, the remote content is not currently available.
</c:if>
<c:import>
操作的最后两个(可选)属性是var
和scope
。 var
属性使从指定URL提取的内容存储(作为String
值)在作用域变量中,而不是包含在当前JSP页面中。 scope
属性控制此变量的作用域,并且默认为页面范围。 正如我们将在后面的文章中看到的那样,JSTL xml
库中的标记充分利用了<c:import>
可以将整个文档存储在范围变量中。
另请注意,(可选)嵌套的<c:param>
标记可用于为要导入的URL指定请求参数。 如同对的情况下<c:param>
标记的嵌套与<c:url>
参数名称和值的URL编码为必要的。
请求重定向
最终的core
库标签是<c:redirect>
。 此操作用于将HTTP重定向响应发送到用户的浏览器,并且等效于javax.servlet.http.HttpServletResponse
的sendRedirect()
方法的JSTL。 清单19中所示,此标记的url
和context
属性的行为与<c:import>
的url
和context
属性的行为相同,任何嵌套的<c:param>
标记的影响也是如此。
清单19. <c:redirect>操作的语法
<c:redirect url="expression" context="expression">
<c:param name="expression" value="expression"/>
...
</c:redirect>
清单20显示了<c:redirect>
操作,该操作用重定向到指定错误页面的方式替换了清单18中的错误消息。 在此示例中, <c:redirect>
标记的使用方式与标准<jsp:forward>
动作类似。 但是请回想一下,通过请求分配器进行的转发是在服务器端实现的,而重定向是由浏览器执行的。 从开发人员的角度来看,转发比重定向更有效,但是<c:redirect>
动作更加灵活,因为<jsp:forward>
只能分派到当前Servlet上下文中的其他JSP页面。
清单20.响应异常而重定向
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
<c:redirect url="/errors/remote.jsp"/>
</c:if>
从用户的角度来看,主要区别在于重定向将更新浏览器显示的URL,因此将影响书签的设置。 另一方面,转发对最终用户是透明的。 因此, <c:redirect>
和<jsp:forward>
之间的选择还取决于所需的用户体验。
摘要
JSTL core
库包含各种通用的自定义标签,这些标签应被广泛的JSP开发人员使用。 例如,URL和异常处理标记很好地补充了现有的JSP功能,例如<jsp:include>
和<jsp:forward>
操作, include
指令和page
指令的errorpage
属性。 迭代和条件操作使得无需脚本元素即可实现复杂的表示逻辑,尤其是与变量标签( <c:set>
和<c:remove>
)和EL结合使用时。
翻译自: https://www.ibm.com/developerworks/java/library/j-jstl0318/index.html