JavaServer Pages(JSP)技术是J2EE平台的标准表示层技术。 JSP技术提供脚本元素和动作,以执行旨在动态生成页面内容的计算。 脚本元素允许程序源代码包含在JSP页面中,以便在响应用户请求而呈现页面时执行。 动作将计算操作封装到标签中,这些标签更类似于通常包含JSP页面模板文本HTML或XML标记。 JSP规范仅定义了少量动作,但是从JSP 1.1开始,开发人员已经能够以定制标记库的形式创建自己的动作。
JSP标准标记库(JSTL)是JSP 1.2定制标记库的集合,这些标记库实现了广泛的服务器端Java应用程序所共有的基本功能。 通过为典型的表示层任务(例如数据格式和迭代或有条件的内容)提供标准的实现,JSTL允许JSP作者专注于特定于应用程序的开发需求,而不是为这些通用操作“费力”。
当然,您可以使用JSP脚本编制元素(脚本,表达式和声明)来实现此类任务。 例如,可以使用清单1中突出显示的三个脚本来实现条件内容。尽管它们依赖于在页面中嵌入程序源代码(通常是Java代码),但是脚本元素往往使JSP页面的软件维护任务变得非常复杂。使用它们。 例如,清单1中的scriptlet示例严重依赖于花括号的正确匹配。 如果无意中引入了语法错误,则在条件化内容中嵌套其他scriptlet可能会造成严重破坏,并且在由JSP容器编译页面时,要弄清产生的错误消息可能是一个很大的挑战。
清单1.通过小脚本实现条件内容
<% if (user.getRole() == "member")) { %>
<p>Welcome, member!</p> <% } else { %> <p>Welcome,
guest!</p> <% } %>
解决此类问题通常需要一定的编程经验。 尽管JSP页面中的标记通常可能由精通页面布局和图形设计的设计人员开发和维护,但是在出现问题时,同一页面中的脚本元素需要程序员的干预。 对单个文件中的代码的共同责任使开发,调试和增强此类JSP页面成为一项繁琐的任务。 通过将通用功能打包到一组标准化的定制标记库中,JSTL使JSP作者可以减少或消除对脚本元素的需求,并避免相关的维护成本。
JSTL 1.0
JSTL 1.0于2002年6月发布,由四个自定义标签库( core
, format
, xml
和sql
)和一对通用标签库验证器( ScriptFreeTLV
和PermittedTaglibsTLV
)组成。 core
标签库提供自定义操作,以通过作用域变量管理数据,以及执行页面内容的迭代和条件化。 它还提供用于生成URL并对其进行操作的标签。 顾名思义, format
标签库定义用于格式化数据(特别是数字和日期)的操作。 它还为使用本地化资源包的JSP页面国际化提供支持。 xml
库包含用于处理通过XML表示的数据的标记,而sql
库定义用于查询关系数据库的操作。
这两个JSTL标记库验证器允许开发人员在其JSP应用程序中实施编码标准。 您可以配置ScriptFreeTLV
验证器,以禁止在JSP页面中使用各种类型的JSP脚本元素-脚本,表达式和声明。 类似地, PermittedTaglibsTLV
验证器可用于限制可以由应用程序的JSP页面访问的自定义标记库(包括JSTL标记库)的集合。
尽管JSTL最终将成为J2EE平台的必需组件,但如今只有少数应用程序服务器包含它。 为JSTL 1.0的参考实现可作为Apache软件基金会的Jakarta Taglibs项目的一部分(见相关主题 )。 可以将参考实现中的定制标记库合并到任何支持JSP 1.2和Servlet 2.3规范的应用服务器中,以添加JSTL支持。
表达语言
在JSP 1.2中,使用静态字符串或表达式(如果允许)指定JSP操作的属性。 例如,在清单2中,为此<jsp:setProperty>
操作的name
和property
属性指定了静态值,而使用表达式指定其value
属性。 该操作具有将请求参数的当前值分配给命名bean属性的效果。 以这种方式使用的表达式称为请求时属性值,并且是JSP规范中内置的用于动态指定属性值的唯一机制。
清单2.包含请求时间属性值的JSP操作
<jsp:setProperty name="user" property="timezonePref"
value='<%= request.getParameter("timezone") %>'/>
因为请求时属性值是使用表达式指定的,所以它们与其他脚本元素一样容易出现相同的软件维护问题。 因此,JSTL定制标记支持用于指定动态属性值的备用机制。 可以使用简化的表达式语言 (EL)来指定JSTL动作的属性值,而不是使用成熟的JSP表达式。 EL提供了标识符,访问器和运算符,用于检索和处理驻留在JSP容器中的数据。 该EL是松散基于ECMAScript(见相关主题 )和XML路径语言(XPath),所以它的语法应该是熟悉的两个页面设计师和程序员。 EL适用于查找对象及其属性,并对它们执行简单的操作。 它不是编程语言,甚至不是脚本语言。 但是,当与JSTL标记结合使用时,它可以使用简单方便的表示法来表示复杂的行为。 EL表达式使用前导美元符号($)以及前括号和后括号({})分隔,如清单3所示。
清单3.一个说明EL表达式分隔符的JSTL操作
<c:out value="${user.firstName}"/>
此外,您可以将多个表达式与静态文本结合起来,以通过字符串串联来构造动态属性值,如清单4所示。单个表达式由标识符,访问器,文字和运算符组成。 标识符用于引用存储在数据中心中的数据对象。 EL具有11个保留的标识符,对应于11个EL隐式对象。 假定所有其他标识符都引用范围变量 。 访问器用于检索对象或集合元素的属性。 文字表示固定值-数字,字符串,布尔值或空值。 运算符允许组合数据和文字并进行比较。
清单4.组合静态文本和多个EL表达式以指定动态属性值
<c:out value="Hello
${user.firstName} ${user.lastName}"/>
范围变量
JSP API通过<jsp:useBean>
动作,允许在JSP容器内的四个不同作用域中存储和检索数据。 JSTL通过提供用于在这些范围内分配和删除对象的附加操作来扩展此功能。 此外,EL提供了内置支持,可将这些对象作为范围变量进行检索。 特别是,出现在EL表达式中且与EL的隐式对象之一不对应的任何标识符将自动假定为引用存储在四个JSP范围之一中的对象:
- 页面范围
- 要求范围
- 会议范围
- 适用范围
您可能还记得,页面范围中存储的对象只能在处理特定请求的页面期间被检索。 可以在处理参与请求的所有页面的过程中检索存储在请求范围中的对象(例如,如果请求的处理遇到一个或多个<jsp:include>
或<jsp:forward>
操作)。 如果对象存储在会话作用域中,则可以由用户在与Web应用程序的单个交互会话中访问的任何页面检索(即,直到与该用户的交互关联的HttpSession
对象无效)。 存储在应用程序范围中的对象可以从所有页面访问,并且对于所有用户都可以访问,直到Web应用程序本身被卸载(通常是由于JSP容器被关闭)。
通过将字符串映射到所需范围内的对象,将对象存储在范围内。 然后,您可以通过提供相同的字符串从作用域中检索对象。 在作用域的映射中查找字符串,并返回映射的对象。 在Servlet API中,此类对象称为相应范围的属性 。 但是,在EL的上下文中,也可以将与属性关联的字符串视为变量的名称,该变量通过属性映射绑定到特定值。
在EL中,假定不与隐式对象关联的标识符为存储在四个JSP范围中的对象命名。 首先根据页面范围,然后是请求范围,然后是会话范围,最后是应用程序范围来检查所有此类标识符,依次测试该标识符的名称是否与该范围内存储的对象的名称匹配。 返回第一个这样的匹配作为EL标识符的值。 通过这种方式,可以将EL标识符视为引用范围变量。
用更专业的术语来说,未映射到隐式对象的findAttribute()
使用PageContext
实例的findAttribute()
方法进行评估,该方法表示对当前正在处理的请求进行表达的页面的处理。 标识符的名称作为该方法的参数传递,该方法依次在四个范围中的每个范围内搜索具有相同名称的属性。 找到的第一个匹配项将作为findAttribute()
方法的值返回。 如果在四个范围的任何一个中都没有找到这样的属性,则返回null
。
最终,范围变量是四个JSP范围的属性,这些属性的名称可用作EL标识符。 只要为它们分配了字母数字名称,就可以通过JSP中用于设置属性的任何机制来创建范围变量。 这包括内置的<jsp:useBean>
操作,以及Servlet API中的几个类定义的setAttribute()
方法。 另外,四个JSTL库中定义的许多自定义标签本身也能够设置用作范围变量的属性值。
隐式对象
表1中列出了11个EL隐式对象的标识符。不要将它们与JSP隐式对象(其中只有9个)混淆,因为这两个对象只有一个是公共的。
表1. EL隐式对象
类别 | 识别码 | 描述 |
---|---|---|
JSP | pageContext | PageContext 实例对应于当前页面的处理 |
范围 | pageScope | 关联页面范围属性的名称和值的Map |
requestScope | Map 请求范围属性的名称和值的Map | |
sessionScope | 关联会话范围属性的名称和值的Map | |
applicationScope | 关联应用程序作用域属性的名称和值的Map | |
请求参数 | param | 一个按名称存储请求参数主要值的Map |
paramValues | 一个将请求参数的所有值存储为String 数组的Map | |
请求头 | header | 一个按名称存储请求标头的主要值的Map |
headerValues | 一个将请求标头的所有值存储为String 数组的Map | |
饼干 | cookie | 一个Map 该Map 按名称存储伴随请求的Cookie |
初始化参数 | initParam | 通过名称存储Web应用程序的上下文初始化参数的Map |
尽管JSP和EL隐式对象只有一个公共对象( pageContext
),但其他JSP隐式对象仍然可以从EL访问。 原因是pageContext
具有用于访问所有其他八个JSP隐式对象的属性。 实际上,这是将其包括在EL隐式对象中的主要原因。
其余所有EL隐式对象都是映射,可用于查找与名称对应的对象。 前四个图表示先前讨论的各种属性范围。 它们可用于在特定范围内查找标识符,而不必依赖EL默认使用的顺序查找过程。
接下来的四个映射用于获取请求参数和标头的值。 由于HTTP协议允许请求参数和标头都为多值,因此每个协议都有一对映射。 每对中的第一个映射仅返回请求参数或标头的主要值,通常无论是在实际请求中首先指定的哪个值。 每对中的第二个映射允许检索参数或标头的所有值。 这些映射中的键是参数或标头的名称,而值是String
对象的数组,其每个元素都是单个参数或标头值。
cookie隐式对象提供对请求设置的cookie的访问。 该对象将与请求关联的所有cookie的名称映射到代表这些cookie属性的Cookie
对象。
最终的EL隐式对象initParam
是一个映射,用于存储与Web应用程序关联的任何上下文初始化参数的名称和值。 通过出现在应用程序的WEB-INF
目录中的web.xml
部署描述符文件指定初始化参数。
存取器
由于EL标识符解析为隐式对象或范围变量(通过属性实现),因此必须将其评估为Java对象。 EL可以自动在其相应的Java类中包装和解开原语(例如,可以将int
强制转换为幕后的Integer
类,反之亦然),但是大多数标识符将是指向成熟Java对象的指针。
结果,通常需要访问这些对象的属性,或者在数组和集合的情况下,访问它们的元素。 EL为此提供了两种不同的访问器-点运算符( .
)和括号运算符( []
)-也使属性和元素也可以通过EL进行操作。
点运算符通常用于访问对象的属性。 例如,在表达式${user.firstName}
,点运算符用于访问由user
标识符引用的对象的名为firstName
的属性。 EL使用Java Bean约定访问对象属性,因此必须定义此属性的getter(通常是名为getFirstName()
的方法),以便此表达式正确求值。 当所访问的属性本身是对象时,可以递归应用点运算符。 例如,如果我们的假设user
对象具有实现为Java对象的address
属性,则点运算符也可以用于访问该对象的属性。 例如,表达式${user.address.city}
将返回此地址对象的嵌套city
属性。
方括号运算符用于检索数组和集合的元素。 对于数组和有序集合(即实现java.util.List
接口的集合),要检索的元素的索引显示在方括号内。 例如,表达式${urls[3]}
返回由urls
标识符引用的数组或集合的第四个元素(EL中的索引从零开始,就像Java语言和JavaScript一样)。
对于实现java.util.Map
接口的集合,括号运算符使用关联的键查找存储在映射中的值。 该键在方括号内指定,并且相应的值作为表达式的值返回。 例如,表达式${commands["dir"]}
返回由commands
标识符引用的Map
中与"dir"
键关联的值。
无论哪种情况,都允许表达式出现在方括号内。 评估嵌套表达式的结果将用作检索集合或数组的适当元素的索引或键。 就像点运算符一样,方括号运算符可以递归应用。 这使EL可以从多维数组,嵌套集合或两者的任意组合中检索元素。 此外,点运算符和括号运算符是可互操作的。 例如,如果数组的元素本身是对象,则可以使用方括号运算符检索数组的元素,并将其与点运算符组合以检索元素的属性之一(例如, ${urls[3].protocol}
)。
考虑到EL作为用于指定动态属性值的简化语言的作用,EL访问器的一个有趣的功能是,与Java语言的访问器不同,它们在应用于null
时不会抛出异常。 如果将EL访问器应用于的对象(例如${foo.bar}
和${foo["bar"]}
的foo
标识符)为null
,则应用访问器的结果也将为null
。 很快就会发现,这在大多数情况下是非常有用的行为。
最后,点运算符和括号运算符可以互换。 例如, ${user["firstName"]}
也可用于检索user
对象的firstName
属性,就像${commands.dir}
可用于获取与对象中"dir"
键相关联的值一样。 commands
图。
经营者
这样,EL可以使用标识符和访问器遍历包含应用程序数据(通过作用域变量公开)或有关环境的信息(通过EL隐式对象)的对象层次结构。 但是,仅访问此类数据通常不足以实现许多JSP应用程序所需的表示逻辑。
为此,EL还包括多个运算符,用于操纵和比较由EL表达式访问的数据。 这些运算符总结在表2中。
表2. EL运算符
类别 | 经营者 |
---|---|
算术 | + , - , * , / (或div ), % (或mod ) |
关系型 | == (或eq ), != (或ne ), < (或lt ), > (或gt ), <= (或le ), >= (或ge ) |
逻辑上 | && (或and ), || (或or ) ! (或not ) |
验证方式 | empty |
算术运算符支持数值的加法,减法,乘法和除法。 还提供了余数运算符。 请注意,除法和余数运算符具有备用的非符号名称(以便与XPath一致)。 清单5中显示了一个演示使用算术运算符的示例表达式。将算术运算符应用于一对EL表达式的结果是将该运算符应用于这些表达式返回的数值的结果。
清单5.利用算术运算符的EL表达式
${item.price * (1 + taxRate[user.address.zipcode])}
关系运算符使您可以比较数字或文本数据。 比较的结果作为布尔值返回。 逻辑运算符允许组合布尔值,并返回新的布尔值。 因此,可以将EL逻辑运算符应用于嵌套的关系或逻辑运算符的结果,如清单6所示。
清单6.利用关系和逻辑运算符的EL表达式
${(x >= min) && (x <= max)}
最终的EL运算符为empty
,这对于验证数据特别有用。 empty
算符采用一个表达式作为其参数(即${empty input}
),并返回一个布尔值,该布尔值指示该表达式是否为“空”值。 计算结果为null
表达式被视为空,没有元素的集合或数组也被视为空。 如果empty
算符的参数计算得出的String
长度为零,则该运算符也将返回true
。
EL运算符的运算符优先级如表3所示。如清单5和6所示,括号可用于对表达式进行分组并覆盖常规优先级规则。
表3. EL运算符的优先级(从上到下,从左到右)
[] . |
() |
一元- not , ! , empty |
* , / , div , % , mod |
+ ,二进制- |
() < , > , <= , >= , lt , gt , le , ge |
== , != , eq , ne |
&& and |
|| or |
文字
数字,字符串,布尔值和null
都可以在EL表达式中指定为文字值。 字符串用单引号或双引号分隔。 布尔值由true
和false
。
Taglib指令
正如我们前面讨论的,JSTL 1.0包含四个自定义标记库。 为了说明JSTL标签与表达语言的交互作用,我们将研究JSTL core
库中的几个标签。 与任何JSP定制标记库一样,必须在要使用此库的标记的任何页面中包含taglib
指令。 清单7中显示了此特定库的指令。
清单7. ELTL版本的JSTL核心库的taglib指令
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"
%>
实际上,有两个与JSTL core
库相对应的taglib
指令,因为在JSTL 1.0中,EL是可选的。 所有四个JSTL 1.0定制标记库都有使用JSP表达式而不是EL来指定动态属性值的备用版本。 因为这些备用库依赖于JSP的更传统的请求时属性值,所以它们被称为RT库,而那些使用表达语言的库被称为EL库。 开发人员使用备用taglib
指令区分每个库的两个版本。 清单8中显示了使用核心库的RT版本的指令。但是,鉴于我们当前对EL的关注,这是这些指令中的第一个。
清单8. JSTL核心库的RT版本的taglib指令
<%@ taglib uri="http://java.sun.com/jstl/core_rt"
prefix="c_rt" %>
变量标签
我们将考虑的第一个JSTL自定义标记是<c:set>
操作。 如前所述,范围变量在JSTL中起着关键作用,并且<c:set>
操作提供了基于标记的机制来创建和设置范围变量。 清单9中显示了此操作的语法,其中var
属性指定了范围变量的名称, scope
属性指示了变量所在的范围,而value
属性指定了要绑定到变量的值。 如果指定的变量已经存在,则将简单地为其分配指示的值。 如果不是,则创建一个新的作用域变量并将其初始化为该值。
清单9. <c:set>操作的语法
<c:set
var="name" scope="scope" value="expression"/>
scope
属性是可选的,默认为page
。
清单10中给出了<c:set>
两个示例。在第一个示例中,将会话范围的变量设置为String
值。 在第二种方法中,使用一个表达式来设置一个数值:一个名为square
的页面范围变量被分配了一个乘以x
本身的请求参数的值的结果。
清单10. <c:set>操作的示例
<c:set
var="timezone" scope="session" value="CST"/> <c:set var="square"
value="${param['x'] * param['x']}"/>
除了使用属性,还可以将范围变量的值指定为<c:set>
操作的主体内容。 使用这种方法,您可以重写清单10中的第一个示例,如清单11所示。此外,正如我们稍后所看到的, <c:set>
标记的主体内容可以采用自定义标记本身也是可以接受的。 在<c:set>
主体内生成的所有内容都将作为String
值分配给指定的变量。
清单11.通过主体内容指定<c:set>操作的值
<c:set var="timezone"
scope="session">CST</c:set>
JSTL核心库包括用于管理作用域变量的第二个标签<c:remove>
。 顾名思义, <c:remove>
操作用于删除作用域变量,并具有两个属性。 var
属性命名要删除的变量,而可选的scope
属性指示应从其删除范围,默认为page
,如清单12所示。
清单12. <c:remove>操作的示例
<c:remove var="timezone" scope="session"/>
输出量
尽管<c:set>
操作允许将表达式的结果分配给作用域变量,但是开发人员通常会希望仅显示表达式的值,而不是存储它。 这就是JSTL的<c:out>
自定义标记的作用,其语法显示在清单13中。该标记评估其value
属性指定的表达式,然后输出结果。 如果指定了可选的default
属性,则在value
属性的表达式计算为null
或空String
default
, <c:out>
操作将打印其值。
清单13. <c:out>操作的语法
<c:out
value="expression" default="expression"
escapeXml="boolean"/>
escapeXml
属性也是可选的。 它控制在通过<c:out>
标记输出时是否应转义在HTML和XML中具有特殊含义的字符,例如“ <”,““>”和“&”。 如果将escapeXml
设置为true,那么这些字符将自动转换为相应的XML实体(此处提到的字符分别为<
, >
和&
)。
例如,假设有一个名为user
的会话范围变量,它是一个类的实例,该类为用户定义了两个属性, username
和company
。 每当用户访问站点时,都会将该对象自动分配给会话,但是在用户实际登录之前,不会设置这两个属性。在这种情况下,请考虑清单14所示的JSP片段。片段将显示单词“ Hello”,后跟他或她的用户名和一个感叹号。 但是,在用户登录之前,此片段生成的内容将改为短语“ Hello Guest!”。 在这种情况下,由于username
属性尚未初始化,因此<c:out>
标记将打印出其default
属性的值(即字符串“ Guest”)。
清单14.具有默认内容的<c:out>操作示例
Hello <c:out value="${user.username}" default="Guest"/>!
接下来,考虑清单15,该清单使用<c:out>
标记的escapeXml
属性。 如果在这种情况下, company
属性已设置为Java String
值"Flynn & Sons"
,则此操作生成的内容实际上将是Flynn & Sons
。 如果此操作是生成HTML或XML内容的JSP页面的一部分,则该字符串中间的&号可能最终被解释为HTML或XML控制字符,并中断了此内容的呈现或解析。 但是,如果将escapeXml
属性的值设置为true
,则生成的内容将改为Flynn & Sons
。 浏览器或解析器遇到此内容时,其解释应该没有问题。 鉴于HTML和XML是JSP应用程序中最常见的内容类型,所以escapeXml
属性的默认值为true
不足为奇。
清单15.禁用转义的<c:out>操作示例
<c:out value="${user.company}" escapeXml="false"/>
使用默认值设置变量
除了简化动态数据的显示外,通过<c:set>
设置变量值时, <c:out>
可以指定默认值的功能也很有用。 如清单11中突出显示的那样,可以将要分配给作用域变量的值以及<c:set>
标记的主体内容以及其value属性指定为该内容。 通过将<c:out>
操作嵌套在<c:set>
标记的主体内容中,变量分配可以利用其默认值功能。
清单16中说明了这种方法。外部<c:set>
标记的行为非常简单:它根据主体内容设置session-scope timezone
变量的值。 但是,在这种情况下,主体内容是通过<c:out>
动作生成的。 此嵌套操作的value属性是表达式${cookie['tzPref'].value}
,它试图通过cookie
隐式对象返回名为tzPref
的cookie
值。 ( cookie
隐式对象将cookie名称映射到相应的Cookie
实例,这意味着您必须使用点运算符通过对象的value
属性检索存储在cookie中的实际数据。)
清单16.组合<c:set>和<c:out>提供默认变量值
<c:set var="timezone" scope=="session">
<c:out value="${cookie['tzPref'].value}" default="CST"/> </c:set>
但是,请考虑这种情况,这是用户使用此代码对Web应用程序的首次体验。 结果,请求中没有提供名为tzPref
cookie。 这意味着使用隐式对象的查找将返回null
,在这种情况下,整个表达式将返回null
。 由于评估其value
属性的结果为null
,因此<c:out>
标记将改为输出评估其default
属性的结果。 在这里,这是字符串CST
。 最终结果是, timezone
范围的变量将设置为存储在用户tzPref
cookie中的时区,或者,如果不存在,则使用默认时区CST
。
摘要
EL与四个JSTL自定义标记库提供的动作配合使用,使页面作者无需借助脚本元素即可实现表示层逻辑。 例如,将本文开头的清单1中的JSP代码与通过清单17中突出显示的JSTL实现的功能进行对比。(JSTL core
库中的其余标签,包括<c:choose>
及其子级尽管仍然很清楚正在执行条件逻辑,但是JSTL版本中没有Java语言源代码,并且标记之间的关系特别是关于嵌套要求,这将在本系列的下一篇文章中介绍。 -任何熟悉HTML语法的人都应该熟悉。
清单17.通过JSTL实现条件内容
<c:choose><c:when test="${user.role ==
'member'}"> <p>Welcome, member!</p>
</c:when><c:otherwise> <p>Welcome,
guest!</p> </c:otherwise></c:choose>
通过提供大多数Web应用程序共有的功能的标准实现,JSTL有助于加快开发周期。 与EL配合使用,JSTL可以消除表示层中对程序代码的需求,从而大大简化了JSP应用程序的维护。
翻译自: https://www.ibm.com/developerworks/java/library/j-jstl0211/index.html