深入核心vcl_深入核心

在本系列的第一篇文章中,您首先了解了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应用程序
Weblog示例应用程序的屏幕截图

尽管完整的实现需要几个Java类,但是在表示层中仅引用了Weblog应用程序的两个类EntryUserBean 。 因此,要理解JSTL示例,只有这两个类很重要。 EntryUserBean类图如图2所示。

图2. Weblog应用程序的类图
Weblog示例应用程序的类图

Entry类表示Weblog中的带日期的条目。 它的id属性用于在数据库中存储和检索条目,而titletext属性代表条目的实际内容。 createdlastModified属性引用了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语言的IteratorEnumeration类)。

为了遍历整数范围,使用了清单1中所示的<c:forEach>标记的语法。 beginend属性应为静态整数值或为整数值的表达式。 它们分别指定了迭代索引的初始值和应该停止迭代的索引值。 使用<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所示。这是通过为beginstep属性都指定值为2,为end属性指定值为10来实现的。 此外, var属性用于创建用于存储索引值的作用域变量,该变量在<c:forEach>标记的正文中被引用。 具体来说,一对<c:out>动作用于显示索引及其平方,后者使用一个简单的表达式进行计算。

图3.清单2的输出
清单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查询中的行

您可以使用beginendstep属性来限制集合中的哪些元素包含在迭代中。 与通过<c:forEach>进行数字迭代的情况一样,当迭代集合的元素时,也会维护迭代索引。 实际上,只有与与指定的beginendstep值匹配的索引值相对应的那些元素才会由<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的输出
清单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的输出
清单5的输出

除了<c:forEach>core库还提供了第二个迭代标签: <c:forTokens> 。 此自定义操作是Java语言的StringTokenizer类的JSTL类似物。 清单6中显示的<c:forTokens>标记与<c:forEach>的面向集合的版本具有相同的属性集,并带有一个附加属性。 对于<c:forTokens> ,要通过items属性指定要标记化的字符串,而通过delims属性提供用于生成标记的定界符集。 与<c:forEach> ,您可以使用beginendstep属性将要处理的标记限制为与相应索引值匹配的标记。

清单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>可以选择通过其varscope属性将测试结果分配给作用域变量(在这里,它们与<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的输出

如清单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>标签表示,其中必须至少有一个。 仅处理testtrue的第一个<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>操作的最后两个(可选)属性是varscopevar属性使从指定URL提取的内容存储(作为String值)在作用域变量中,而不是包含在当前JSP页面中。 scope属性控制此变量的作用域,并且默认为页面范围。 正如我们将在后面的文章中看到的那样,JSTL xml库中的标记充分利用了<c:import>可以将整个文档存储在范围变量中。

另请注意,(可选)嵌套的<c:param>标记可用于为要导入的URL指定请求参数。 如同对的情况下<c:param>标记的嵌套与<c:url>参数名称和值的URL编码为必要的。

请求重定向

最终的core库标签是<c:redirect> 。 此操作用于将HTTP重定向响应发送到用户的浏览器,并且等效于javax.servlet.http.HttpServletResponsesendRedirect()方法的JSTL。 清单19中所示,此标记的urlcontext属性的行为与<c:import>urlcontext属性的行为相同,任何嵌套的<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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值