通过避免使用脚本元素来简化 JSP 应用程序的软件维护。
J**Aserver Pages (JSP) 是用于 J2EE 平台的标准表示层技术。 JSP 技术提供了脚本元素和操作,用于执行动态生成页面内容的计算。 scripting 元素允许您在 JSP 页面中包含程序源,当页面呈现以响应用户请求时,可以执行该程序源。 操作将计算操作封装到标记中,这与 HTML 或 XML 标记非常相似,这些标记通常包含在 JSP 页面的模板文本中。 JSP 规范仅将少数操作定义为标准操作,但从 JSP 1 开始1 首先,开发人员已经能够以自定义标记库的形式创建自己的操作。
JSP 标准标记库 (JSTL) 是 JSP 12 自定义的标记库集,用于实现大量服务器端 j**a 应用程序常用的基本功能。 通过为典型的表示层任务(如数据格式化和迭代或条件内容)提供标准实现,JSTL 允许 JSP 作者专注于特定于应用程序的开发需求,而不是为这些通用操作“重新开始”。
当然,您可以使用 JSP 脚本元素(scriptlet、表达式和声明)来实现此类任务。 例如,可以使用三个 scriptlet 实现条件内容,它们在清单 1 中突出显示。 但是,由于脚本元素依赖于在页面中嵌入程序源(通常是 j**a),因此使用这些脚本元素的 JSP 页面的软件维护任务的复杂性大大增加。 例如,清单 1 中的 scriptlet 示例严格依赖于大括号的正确匹配。 如果无意中引入了语法错误,那么将其他 scriptlet 嵌套在条件内容中可能会造成严重破坏,并且在 JSP 容器编译页面时可能很难理解生成的错误消息。
清单 1通过 scriptlet 实现条件内容。
<% if (user.getrole() == "member")) else %
解决此类问题通常需要相当多的编程经验。 虽然 JSP 通常由精通页面布局和图形设计的设计师开发和维护,但当同一页面的脚本元素出现问题时,程序员需要进行干预。 这种情况将单个文件的责任分散到多个人之间,使开发、调试和增强此类 JSP 页面成为一项繁琐的任务。 通过将常用功能包装到自定义标记库的标准集合中,JSTL 使 JSP 作者能够减少对脚本元素的需求,甚至消除它们,并避免相关的维护成本。
jstl 1.0 于 2002 年 6 月发布,由四个自定义标记库 (core
format
xml
跟sql
) 和一对通用标签库验证器 (scriptfreetlv
跟permittedtaglibstlv
组成。 core
标记库提供自定义操作,使用作用域变量管理数据,并对页面内容执行迭代和条件操作。 它还提供用于生成和操作 URL 的标记。 顾名思义,format
标记库定义用于格式化数据的操作,尤其是数字和日期。 它还支持使用本地化资源包对 JSP 页面进行国际化。 xml
该库包含用于操作由 XML 表示的数据的标记sql
该库定义用于查询关系数据库的操作。
两个 JSTL 标记库验证器允许开发人员在其 JSP 应用程序中强制使用编码标准。 可配置scriptfreetlv
验证程序,用于在 JSP 页面中禁用各种类型的 JSP 脚本元素、scriptlet、表达式和声明。 同样地permittedtaglibstlv
验证程序可用于限制应用程序的 JSP 页面可以访问的自定义标记库(包括 JSTL 标记库)的集合。
尽管 JSTL 最终将成为 J2EE 平台的必需组件,但目前只有少数应用程序服务器包含它。 jstl 1.0 的参考实现作为 Apache 软件基金会的 Jakarta Taglibs 项目的一部分提供(参见 参考资料)。 您可以将该参考实现中的自定义标记库合并到对 JSP 1 的任何支持中2 和 servlet 23 Spec Server,用于添加对 JSTL 的支持。
在 JSP 1 中2、您可以使用静态字符串或表达式(如果允许)来指定 JSP 操作的属性。 例如,在清单 2 中,右操作
name
跟property
该属性指定一个静态值,同时使用表达式指定该值value
属性。 这样做的效果是将请求参数的当前值分配给命名 Bean 属性。 此表单中使用的表达式称为请求时间属性值,这是 JSP 规范中内置的用于动态指定属性值的唯一机制。
清单 2合并请求时属性值的 JSP 操作。
由于请求时属性值由表达式指定,因此它们通常会遇到与其他脚本元素相同的软件维护问题。 因此,JSTL 自定义标记支持另一种用于指定动态属性值的机制。 您可以使用简化表达式语言 (EL) 而不是完整的 JSP 表达式来指定 JSTL 操作的属性值。 EL 提供标识符、访问器和运算符,用于检索和操作驻留在 JSP 容器中的数据。 EL 在某种程度上是基于 ECMASCRIPT(参见 参考资料)和 XML 路径语言 (XPATH),因此页面设计人员和程序员都应该熟悉它的语法。 EL 擅长查找对象及其属性,然后对它们执行简单的操作; 它不是一种编程语言,甚至不是一种脚本语言。 但是,当与 JSTL 标记一起使用时,它可用于使用简单方便的符号来表示复杂的行为。 el 表达式的格式如下:用美元符号 ($) 分隔,用大括号 ({}) 括起来,如清单 3 所示。
清单 3描述 EL 表达式分隔符的 JSTL 操作。
此外,您可以将多个表达式与静态文本组合在一起,通过字符串并列来构造动态属性值,如清单 4 所示。 单独的表达式由标识符、访问器、文本和运算符组成。 标识符用于指代存储在数据中心的数据对象。 EL 有 11 个保留标识符,对应 11 个 EL 隐式对象。 假设所有其他标识符都引用限制范围的变量。 访问器用于检索对象的属性或集合的元素。 文本表示固定值,例如数字、字符、字符串、布尔值或 null 值。 运算符允许对数据和文本进行组合和比较。
清单 4将静态文本和多个 EL 表达式组合在一起以指定动态属性值。
JSP API 通过
操作允许从 JSP 容器中的四个不同作用域存储和检索数据。 JSTL 通过提供用于指定和删除这些作用域中的对象的其他操作来扩展此功能。 此外,EL 还提供了内置支持,用于将这些对象检索为作用域约束变量。 具体而言,任何出现在 EL 表达式中但与任何 EL 隐式对象不对应的标识符都会自动假定为引用存储在以下四个 JSP 作用域之一中的对象,这些作用域是:
页面范围请求范围会话范围应用程序范围您可能还记得,存储在该页面范围中的对象只能在处理该请求的页面期间检索。 如果对象存储在请求的作用域中,则可以在处理请求处理中涉及的所有页面(例如,在处理请求时遇到的一个或多个页面)期间检索这些对象。或
action)。如果对象存储在会话作用域中,则用户在与 Web 应用程序的交互式会话期间访问的任何页面(即,直到它与该用户交互关联)都可以检索该对象
httpsession
对象无效)。任何用户都可以从任何页面访问存储在应用程序作用域中的对象,直到卸载 Web 应用程序本身(通常是由于关闭了 JSP 容器)。
通过将字符串映射到该作用域中的对象,将对象存储到作用域。 然后,可以通过提供相同的字符串从该范围中检索对象。 在作用域的映射中查找字符串,并返回映射的对象。 在 Servlet API 中,此类对象称为相应作用域的属性。 但是,在 EL 的上下文中,与属性关联的字符串也被视为变量的名称,该变量通过属性映射获取特定值。
在 EL 中,未与隐式对象关联的标识被视为存储在四个 JSP 作用域中的名称对象。 首先检查页面范围的此类标识符,然后检查请求范围,然后检查会话范围,然后检查应用程序范围,然后测试标识符的名称是否与存储在该范围中的对象的名称匹配。 第一个此类匹配项作为 el 标识符的值返回。 这样,您可以将 EL 标识符视为对限制范围的变量的引用。
在更技术层面上,没有映射到所使用的隐式对象的标识符pagecontext
实例findattribute()
方法计算,表示当前正在处理请求表达式的页面的处理。 标识符的名称将作为参数传递给方法,然后方法在四个作用域中的每一个作用域中搜索具有相同名称的属性。 并将找到的第一个匹配项作为findattribute()
返回该方法的值。 如果在这四个作用域中找不到此类属性,则返回该属性null
归根结底,限制作用域的变量是四个 JSP 作用域的属性,这些作用域的名称可用作 EL 标识符。 只要为限定为变量的变量提供字母数字名称,就可以通过 JSP 中用于设置属性的任何机制来创建它们。 这包括内置的操作,以及由 Servlet API 中的多个类定义的操作
setattribute()
方法。 此外,四个 JSTL 库中定义的许多自定义标记本身都能够设置用作作用域变量的属性值。
表 1 中列出了 11 个 EL 隐式对象的标识符。 不要将这些对象与 JSP 隐式对象(总共只有 9 个)混淆,其中只有一个是它们共有的。
表1el 隐式对象。
类别标识符和描述 jspPageContext:PageContext 实例对应当前页面的处理。 作用域内页面范围:与页面范围属性的名称和值关联的映射类。 RequestScope:与请求范围属性的名称和值关联的 MAP 类。 SessionScope:与会话范围属性的名称和值关联的映射类。 Applicationscope:与应用程序的 scope 属性的名称和值关联的映射类。 请求参数:按名称存储请求参数主值的映射类。 paramvalues:一个映射类,用于将请求参数的所有值存储为字符串数组。 请求标头:按名称存储请求标头的主要值的映射类。 HeaderValues:一个映射类,用于将请求标头的所有值存储为字符串数组。 cookiecookie:一个映射类,用于按名称存储附加到请求的 cookie。 初始化参数 initparam:一个映射类,用于按名称存储 Web 应用程序上下文的初始化参数。
jsppagecontext pagecontext
实例对应当前页面的处理范围pagescope
与页面范围属性关联的名称和值map
类requestscope
与请求范围属性关联的名称和值map
类sessionscope
与会话范围属性关联的名称和值map
类applicationscope
与应用程序的作用域属性的名称和值相关联map
类请求参数param
按名称存储请求参数的 main 值map
类paramvalues
将请求参数的所有值视为string
数组map
类请求标头header
按名称存储请求标头的主要值map
类headervalues
将请求标头的所有值视为string
数组map
类 Cookiecookie
按名称显示 Cookie 的存储请求map
类初始化参数initparam
按名称存储 Web 应用程序上下文初始化参数map
类
虽然只有一个公共对象(在 JSP 和 EL 隐式对象中pagecontext
),但其他 JSP 隐式对象也可以通过 EL 访问。 原因是pagecontext
访问所有其他 8 个 JSP 隐式对象的功能部件。 事实上,这是将其包含在 el 隐式对象中的主要原因。
所有剩余的 el 隐式对象都是映射,可用于查找与名称相对应的对象。 前四个映射表示前面讨论的各种属性范围。 您可以使用它们来查找特定范围内的标识符,而不是依赖于 EL 默认使用的顺序查找过程。
接下来的四个映射用于获取请求参数和请求标头的值。 由于 HTTP 协议允许请求参数和请求标头的多个值,因此每个值都有一对映射。 每对中的第一个映射返回请求参数或标头的主要值,通常是在实际请求中首先指定的值。 每对中的第二个映射允许检索参数或标头的所有值。 这些映射中的键是参数或标头的名称,但这些值是string
对象数组,其中每个元素都是单个参数值或标头值。
Cookie 隐式对象提供对请求设置的 Cookie 名称的访问。 此对象将与请求关联的所有 Cookie 名称映射到表示这些 Cookie 的名称cookie
对象。
最后一个 el 隐式对象initparam
是一个映射,用于存储与 Web 应用程序关联的所有上下文的初始化参数的名称和值。 初始化参数已传递web.xml
部署描述符文件位于应用程序中web-inf
目录。
由于 el 标识符被解析为隐式对象或作用域变量(通过属性),因此有必要将它们转换为 j**a 对象。 EL 可以在其相应的 J**a 类中自动包装和解包其基本类型(例如,您可以在后台设置它int
强制转换为integer
类,反之亦然),但大多数标识符将是指向完整 J**a 对象的指针。
因此,这些对象的属性或(在对象是数组和集合的情况下)对其元素的访问通常是令人满意的。 为此,EL 提供了两个不同的访问器(点运算符 () 和方括号运算符 (
还支持通过 EL 操作属性和元素。
点运算符通常用于访问对象的属性。 例如,在表达式中,使用点运算符来访问它
user
标识符所引用的对象的名称firstname
特性。 EL 使用 j**a Bean 约定来访问对象属性,因此必须为此属性定义一个 getter 方法(通常命名为getfirstname()
以便正确计算表达式。 当被访问的属性本身是对象时,可以递归应用点运算符。 例如,如果我们编造一些东西user
该对象具有 j**a 对象的实现address
属性,则还可以使用 DOT 运算符来访问此对象的属性。 例如,表达式将返回嵌套的此地址对象
city
特征。
方括号运算符用于检索数组和集合的元素。 在数组和有序集合中(即,实现j**a.util.list
interface),将要检索的元素的下标放在方括号内。例如,表达式返回
urls
标识符是指数组或集合的第四个元素(如在 J**a 语言和 J**Ascript 中,EL 中的下标从零开始)。
用于实施j**a.util.map
方括号运算符是接口的集合,使用关联的键来查找存储在映射中的值。 在方括号中指定键,并返回相应的值作为表达式的值。 例如,表达式返回
commands
标识符map
"dir"
与键关联的值。
在这两种情况下,您都可以允许表达式显示在方括号中。 计算嵌套表达式的结果将用作下标或键,以检索集合或数组的相应元素。 与点运算符一样,方括号运算符可以递归应用。 这使 EL 能够从多维数组、嵌套集合或两者的任意组合中检索元素。 此外,点运算符和括号运算符是可互操作的。 例如,如果数组的元素本身就是对象,则可以使用括号运算符来检索数组的元素,并结合点运算符来检索元素的某个属性(例如
假设 EL 充当指定动态属性值的简化语言,则 EL 访问器(与 j**a 访问器不同)的一个有趣的功能是它们应用于null
不会引发异常。 例如,如果应用应用了 EL 访问器的对象,跟
foo
identifier) 是null
,则应用程序访问器的结果也是null
。在大多数情况下,这是一个非常有用的行为,用不了多久你就会明白这一点。
最后,可以在点括号和方括号运算符之间实现一定程度的互换。 例如,它也可以使用检索
user
对象firstname
功能,可以使用获取
commands
在映射中"dir"
这些键与相同的值相关联。
EL 还可以使用标识符和访问器遍历包含应用程序数据(由作用域变量公开)或有关环境的信息(通过 EL 隐式对象)的对象层次结构。 但是,仅仅访问此数据通常不足以实现许多 JSP 应用程序所需的表示逻辑。
最终,EL 还包括几个运算符,用于操作和比较 EL 表达式访问的数据。 表 2 总结了这些运算符。
表2EL 运算符。
类别运算符、算术运算符或
div
跟或
mod
关系运算符或
eq
或
ne
或
lt
或
gt
或
le
跟或
ge
逻辑运算符或
and
或or
跟或
not
) 验证操作员empty
算术运算符支持数值的加法、减法、乘法和除法。 还提供了一个余数运算符。 注意:除法和余数运算符都有备用的、非符号名称(与 xpath 一致)。 清单 5 显示了一个演示使用算术运算符的示例表达式。 将算术运算符应用于多个 EL 表达式的结果就是将该算术运算符应用于这些表达式返回的数值的结果。
清单 5使用算术运算符的 EL 表达式。
关系运算符允许您比较数字或文本数据。 比较结果以布尔值的形式返回。 逻辑运算符允许合并布尔值,返回新的布尔值。 因此,您可以将 EL 逻辑运算符应用于嵌套关系或逻辑运算符的结果,如清单 6 所示。
清单 6使用关系运算符和逻辑运算符的 EL 表达式。
最后一种类型的 el 运算符是empty
,这对于验证数据特别有用。 empty
运算符将单个表达式作为其变量(即
),并返回一个布尔值,该值指示计算表达式的结果是否为“null”值。评估的结果是null
表达式被视为 null,即没有元素的集合或数组。 如果参数的长度为零string
评估结果,则empty
操作员也将被退回true
在 el 表达式中,数字、字符串、布尔值和null
可以指定为文本值。 字符串可以用单引号或双引号分隔。 布尔值指定为true
跟false
正如我们之前所讨论的,JSTL 10 包括四个自定义标记库。 为了演示 JSTL 标记和表达式语言之间的交互,我们将查看 JSTL 中的几个数据core
库的标记。 与任何 JSP 定制标记库一样,它必须包含在要使用此库标记的任何页面中taglib
伪指令。 清单 7 显示了用于此特定库的伪指令。
清单 7JSTL Core 库的 EL 版本的 Taglib 伪指令。
<%@taglib uri="" prefix="c" %>
实际上,对应于 jstlcore
图书馆taglib
伪指令有两种,如 jstl 1 所示0,el是可选的。 所有四个 JSTL 10 自定义标记库具有使用 JSP 表达式而不是 EL 来指定动态属性值的备用版本。 由于这些备用库依赖于 JSP 更传统的请求时间属性值,因此它们称为 RT 库,而使用表达式语言的库称为 EL 库。 开发人员使用不同的taglib
伪指令,用于区分每个库的这两个版本。 清单 8 显示了一个使用 Core 库的 RT 版本的伪指令。 但是,由于我们现在讨论的重点是 EL,因此首先需要这些伪指令。
清单 8JSTL Core 库的 RT 版本的 Taglib 伪指令。
<%@taglib uri="_rt" prefix="c_rt" %>
我们要考虑的第一个 JSTL 自定义标记是:
action)。如前所述,作用域变量在 JSTL 中起着关键作用
操作提供了一种基于标记的机制来创建和设置作用域变量。 该操作的语法如清单 9 所示,其中:var
该属性指定作用域内变量的名称scope
该属性指示变量所在的作用域value
该属性指定分配给变量的值。 如果指定的变量已存在,只需为其分配指定的值即可。 如果不存在,请创建一个新的作用域变量,并使用该值初始化该变量。
清单 9操作的语法。
scope
该属性是可选的,其默认值为page
如清单 10 所示两个例子。 在第一个示例中,将会话范围变量设置为:
string
价值。 在第二个示例中,使用表达式设置值:命名页面范围square
该变量名为x
请求参数值的平方。
清单 10操作示例。
您还可以将作用域有限的变量的值指定为
操作的主要内容,而不是使用属性。 使用这种方法,您可以重写清单 10 中的第一个示例,如清单 11 所示。 此外,正如我们马上看到的,
标签本身的正文内容也可以与自定义标签一起使用。
正文中生成的所有内容都将创建一个string
将值分配给指定的变量。
清单 11由正文内容指定操作的值。
cst
JSTL Core 库包含第二个标记,用于管理作用域内的变量
。顾名思义,
该操作用于删除具有有限作用域的变量,并获取两个属性。 var
attribute 指定要删除的变量的名称scope
该属性是可选的,它指示要删除的变量来自的作用域,默认值为page
如清单 12 所示。
清单 12操作示例。
虽然
操作允许将表达式结果分配给作用域内的变量,但开发人员通常希望仅显示表达式的值,而不存储它。 jstl
自定义标记承担了此任务,其语法如清单 13 所示。 标签对由它决定value
计算属性指定的表达式,并打印结果。 如果指定了可选属性default
好吧,在右边value
属性表达式的计算结果为null
或空string
箱
打印其值。
清单 13操作的语法。
escapexml
属性也是可选的。 它控制何时使用
标记在输出字符(如“<”和“&”)时是否应转义,这些字符在 HTML 和 XML 中具有特殊含义。 如果会的话escapexml
设置为 true,这些字符将自动转换为相应的 XML 实体(此处提到的字符将相互转换
跟
例如,假设有一个名为user
,它是为用户定义两个属性的类的实例:username
跟company
。每当用户访问站点时,都会自动将此对象分配给会话,但在用户实际登录之前不会设置这两个属性。 假设这种情况,请考虑清单 14 中的 JSP 片段。 用户登录后,代码段将显示单词“hello”,后跟他或她的用户名和一个感叹号。 但是,在用户登录之前,此代码段生成的内容是短语“hello guest!”。”。在这种情况下,因为username
功能尚未初始化,因此标记将被打印出来
default
属性的值(即字符串“guest”)。
清单 14使用默认内容操作示例。
hello !
接下来,考虑清单 15,它使用
标记escapexml
属性。 如果在这种情况下,它已经是company
该属性设置为 j**astring
价值。 "flynn & sons"
,那么,实际上,操作生成的内容将是flynn & sons
。如果这是生成 HTML 或 XML 内容的 JSP 页面的一部分,那么字符串中间的“&”符号最终可能会被解释为 HTML 或 XML 控件字符,从而阻止显示或解析该内容。 但是,如果您愿意escapexml
属性值设置为true
,生成的内容将是flynn & sons
。浏览器或解析器在解释此类内容时不会遇到问题。 假设 HTML 和 XML 是 JSP 应用程序中最常见的内容类型,因此escapexml
该属性的默认值为true
这并不奇怪。
清单 15禁用转义操作示例。
除了简化动态数据的显示外,当通过
设置变量值时
指定默认值的功能也很有用。 如清单 11 所示可以指定用于分配给限制范围的变量的值,如“按正文内容指定”操作的值所示
标记的正文内容,也可以由其 value 属性指定。 通过愿意
操作嵌套在
标记,变量赋值可以利用其默认值功能。 此方法如清单 16 所示。 外部
标记的行为非常简单:它根据其正文内容设置会话范围timezone
变量的值。 但是,在这种情况下,主要内容被传递
操作生成。 此嵌套操作的 value 属性是一个表达式
,它试图通过cookie
隐式对象返回一个名为tzpref
Cookie 值。 (cookie
隐式对象将 Cookie 名称映射到相应的 Cookie 名称cookie
实例,这意味着它必须通过对象的value
功能使用点运算符来检索存储在 Cookie 中的实际数据。 )
清单 16合并跟
以提供默认变量值。
但是,请考虑以下情况,其中用户首次尝试使用此 Web 应用程序。 因此,请求中没有规定将名称命名为tzpref
饼干。 这意味着将返回使用隐式对象的查找null
,在这种情况下,将返回整个表达式null
。因为是的
标记value
物业评估的结果是:null
所以
然后将标记输出给它们default
财产评估的结果。 在本例中,字符串cst
。因此,实际结果是它将timezone
作用域变量设置为 usertzpref
Cookie 中存储的时区,如果不是,则为默认时区cst
EL 和 JSP 20
目前,表达式语言只能用于在 jstl 自定义标签中指定动态属性值。 但是 JSTL 1有人提议对 0 表达式语言进行扩展,将其包含在 JSP 2 中0 在审查中,目前正在进行最终审查。 此扩展将允许开发人员通过他们自己的自定义标记使用 EL。 页面作者将能够在当前允许 JSP 表达式的任何位置使用 EL 表达式,例如在模板文本中插入动态值:
your preferred time zone is $
这个 jsp 20 特性(就像 JSTL 本身一样)将允许页面作者进一步减少对 JSP 脚本元素的依赖,从而提高 JSP 应用程序的可维护性。
EL(与四个 JSTL 自定义标记库提供的操作相结合)允许页面作者在不使用脚本元素的情况下实现表示层逻辑。 例如,比较本文开头的清单 1这些 scriptlet 用于在条件内容和 JSTL 中实现与 JSP 相同的功能,如清单 17 所示。 (jstlcore
库中的其余标记,包括:及其子标签,将在本系列的下一篇文章中讨论。 虽然条件逻辑显然是执行的,但 JSTL 版本中没有 j.a. 源,任何精通 HTML 语法的人都应该熟悉标记之间的关系(尤其是关于嵌套要求)。 清单 17合并
跟
以提供默认变量值。
welcome, member!
welcome, guest!
通过提供大多数 Web 应用程序通用功能的标准实现,JSTL 有助于加快开发周期。 结合 EL,JSTL 消除了对表示层进行编程的需要,这大大简化了 JSP 应用程序的维护。
使用自定义标记控制 JSP 页面JSP 标记库:为提高可用性而设计 JSP 标准标记库主页Jakarta TaglibsJSTL 在行动 使用自定义标记控制 JSP 页面。