Head First JSP---随笔七
强大的定制标记(JSTL)
有时只是EL或标准动作还不够。如果我们想循环处理一个数组中的数据,并在一个HTML表中每一行显示一项,该怎么做?这时,就需要强大的JSTL了。
使用标记库建立JSP页面
9.1 描述“taglib”指令的语法和语义:包括标准标记库的相应“taglib”指令和标记文件的相应“taglib”指令。
9.2 创建定制标记结构来支持给定的设计目标。
9.3 对于以下JSP标准标记库(JSTL v1.1)标记,明确这些标记的语法,并描述标记的动作语义
1. 核心标记:out,set,remove和catch
2. 条件标记:if,choose,when和otherwise
3. 循环标记:forEach
4. URL相关的标记:url
安装JSTL 1.1
使用c:out标记
可以显式地声明转换XML实体:<c:out value="${perfix.name} escapeXml="true"/>
,则说明将EL表达式的HTML按文本显示。
可以显式地声明不转换XML实体:<c:out value="${perfix.name}" escapeXml="false"/>
,则说明将EL表达式所获取到的HTML标记都进行计算,不按文本显示(例如:EL获得到的字符串是<br/><br/>
,则将计算出2个换行)。
HTML需要转换的特殊字符:
这里指出:escapeXml默认为true。
跨网站攻击(cross-site hacking)或跨网站脚本攻击(cross-site scripting)
这种攻击是指一个用于使用你的Web应用作为传输机制向另一个用户的web浏览器发动攻击。
如图:
default属性设置一个默认值
假设我们在EL表达式计算为null的时候希望打印出一个默认值。
有两种做法:
(1)<c:out>
单标签提供一个default属性,是<c:out value="${user}" default="guest"/>
这样的形式。
(2)<c:out>
双标签可以这样做:<c:out value="${user}">guest</c:out>
。
不用脚本(scriptlet)循环的c:forEach标记
注意:元素和数组。
c:forEach剖析
<c:forEach>
标记可以循环数组和集合中的每一个元素:(如下)
还有一个循环计数器(varStatus):
记住还需要count。
嵌套c:forEach标记
注意嵌套时的属性值。
JSTL注意的几点
1.问:你怎么知道”varStatus”属性是这样一个类的实例,你是怎么知道它有一个”count”性质的?
答:JSTL 1.1规范告诉我的。
2.问:能改变迭代的步长吗?例如i+=3?
答:可以的,JSTL 1.1中有这些问题的答案,改变步长可以使用step属性。
3.一个需要注意的地方:作用域!
c:if条件包含
所有人都得到同样的页面,但是成员在页面上能“看到”更多东西。
这里指出:并没有一个“else”的字句。
c:choose标记
c:set标记的var
“var”版本用于设置属性变量(pageScope,requestScope,applicationScope,session)。
这里指出:var必须是一个直接量,不能使EL表达式!
c:set标记的target
“target”版本用于设置对象性质(bean,Map)
c:set的要点和技巧
-
<c:set>
中不能同时有“var”和“target”属性。 - “scope”是可选的,如果没有使用这个属性,则默认为页面(page)作用域。
- 如果“value”为null,“var”指定的属性将被删除。
- 如果“var”指定的属性不存在,则会创建一个属性,但仅当“value”不为null时才会创建新属性。
- 如果“target”表达式为null,容器会抛出一个异常。
- “target”中要放入一个能解析为实际对象的表达式。如果放入一个String直接量(表示bean或Map的“id”名),这是不行的。换句话或,“target”并非用来放bean或Map的属性名,而是用于指定具体的属性对象(target的值可以是一个bean或Map,但是不能为直接量,下面的话验证了这点)。
- 如果“target”表达式不是一个Map或bean,容器会抛出一个异常。
- 如果“target”表达式是一个bean,但是这个bean没有与“property”匹配的性质,容器就会抛出一个异常。记住EL表达式
${bean.property}
也会抛出一个异常。
c:remove标记
c:import标记
<c:import>
标记让我们只要知道url就可以把别人的页面当做自己页面的一部分(注意最后一行)。
c:param标记
与<jsp:param>
类似。
c:url可以满足所有超链接需求
如果客户浏览器不支持cookie,我们就需要url重写。
这样方便多了。
如果URL需要编码
建立自己的错误页面
在DD中配置错误页面
如果像上面那样写的话,需要在所有的JSP文件中写上指定的错误页面,岂不是要累死?
这个时候DD中允许你配置错误页面,对所有的JSP而言。
在DD中可以根据<exception-type>
或状态码<error-code>
声明错误页面。
错误页面得到一个exception的对象
这样,对我们页面排错就有了可靠的工具。
c:catch来恢复页面,就像try/catch一样
一个<c:catch>
会同时作为try和catch部分,这听上去有点怪,确实没有单独的try标记。我们要把有风险的EL或标记调用(或其他部分)包在<c:catch>
的体中,异常就会在这里被捕获。不过,不能把它当成一个catch块,因为一旦异常发生,控制就会跳至<c:catch>
标记体的最后。
可以将异常设置为一个属性
对于Web应用错误处理,只有正式指定错误页面(isErrorPage=true)才能得到异常对象。
如果想要在<c:catch>
标记结束后访问异常,可以使用可选的var属性。
c:catch注意
JSTL的标记库
JSTL相当庞大。1.1版本有5个库,其中4个是定制标记库,还有一个提供了大量用于串处理的函数。
使用非JSTL的标记库
创建标记的支持代码并不轻松(支持代码就是在JSP中放入标记时所需要调用的Java代码)。
要使用定制库,必须阅读TLD。我们想知道的一切都在这里。
理解TLD
可以看出每个标记都对应着一个name和一个类和体中能否为空,并且可以设置它的属性以及这个属性能否是一个表达式。
使用定制“advice”标记
“advice”标记时一个简单的标记,它有一个属性(用户名),并打印一个随机的建议。这非常简单,使用一个普通的EL函数(有一个静态方法 getAdvice(String name))就完全可以做到,但我们还是把它做成一个简单的标记。
定制标记处理器
这个简单的标记处理器拓展了SimpleTagSupport,而且实现了两个关键方法:doTag()和setUser,doTag()是完成具体工作的方法,setUser()方法接收属性值。
可以知道如果利用定制标记,方法名就必须是doTag()。也就是说,一个标记对应一个类。
注意rtexprvalue
如果<rtexprvalue>
为false,或者未定义<rtexprvalue>
,那么属性值只能是一个String直接量。
还需要注意属性类型:
rtexprvalue不只是针对EL表达式
可以使用3种表达式:
标记体能放什么?
首先看看TLD中标记配置<body-content>
能放什么
对于没有体的标记,有3种调用方法
标记处理器、TLD和JSP
标记处理器开发人员要创建TLD,告诉容器和JSP开发人员如何使用这个标记。JSP开发人员并不关心TLD的<tag-class>
元素;这是容器要考虑的事情。JSP开发人员主要关心uri、标记名和标记语法。标记能有体吗?属性必须是一个String直接量吗?属性能使一个表达式吗?这个属性是可选的吗?表达式要计算为何种类型?
要部署和运行一个使用了标记的Web应用,只需要这3部分——标记处理器类、TLD和JSP。
taglib的uri属性只是一个名,而不是一个位置
TLD中的<uri>
要与taglib指令中的uri匹配!
容器建立一个映射
在JSP 2.0之前,开发人员必须为TLD中的<uri>
与TLD文件的具体位置之前指定一个映射。例如一个JSP页面有<%@ taglib prefix="mine" uri="randomThings"%>
,则部署描述文件(web.xml)必须告诉容器到哪里找有匹配的TLD文件,如下:
在JSP 2.0出现后,DD中已经没有<taglib>
标签了!所以,容器会自动建立TLD和<uri>
名之间的映射。
这是怎么做到的?答案是:在能放TLD的所有特定位置上找一遍。部署一个Web应用时,只要把TLD放在容器会搜索位置上,容器就会发现这个TLD,并为标记库建立一个映射。
容器会在4个位置查找TLD
如果JSP使用了多个标记库
要记住几点:
- 确保taglib uri名是唯一的。换句话说,多个指令不能有相同的uri值。
- 不要使用保留的前缀。
本章完
因为感冒,所以看这章的时候有点累。。不过加油!!