Head First JSP---随笔八(简单标记)
定制标记开发
有时JSTL和标准动作还不够。构建自己的标记处理器有3种不同的方法。在这3种方法中,有两种(简单标记和标记文件)是在JSP 2.0新引入的。
建立定制标记库
10.1 描述执行各个事件方法(doStartTag()、doAfterBody()和doEndTag())时“传统”定制标记事件模型的语义;解释各事件方法返回值的含义,并编写标记处理器类。
10.2 使用PageContext API,编写标记处理器来访问JSP隐式变量以及Web应用属性。
10.3 给定一个场景,编写标记处理器代码来访问父标记和任意的祖先标记。
10.4 描述执行事件方法(doTag())时“简单”定制标记事件模型的语义;编写一个标记处理器;并解释标记中对JSP内容的限制。
10.5 描述标记文件模型的语义:描述标记文件的Web应用结构;编写一个标记文件;解释标记体中对JSP内容的限制。
标记文件
利用标记文件,可以使用一个定制标记调用可重用的内容,而不是使用通用的<jsp:include>
或<c:import>
。可以把标记文件看做是一种“轻型标记处理器”,这是因为页面开发人员利用标记文件创建定制标记,而不用表写复杂的Java标记处理类,但是标记文件只是“美化了”的include。
对于标记文件,发送的不是请求参数,而是标记属性!
标记文件使用attribute指令
与TLD类似,需要指明标记的属性的属性名是什么,是否可选的,是否可以是一个EL**表达式**。
如果属性值很大
假设有一个标记属性很长,甚至是一段文字。我们就可以使用<jsp:doBody>
。
声明标记文件的body-content
使用tag指令,有一个属性“body-content”,默认值为“scriptless”。还有另外2个属性值:empty和tagdependent。如果值为scriptless,这说明不能有脚本元素(<%...%>
)。
在标记文本的体中不能使用脚本代码!
容器在哪里查找标记文件
Jar中标记文件的TLD
问:你说过,标记文件没有TLD,还记得吗?不就是因为这个原因才要使用attribute指令吗?因为你没办法在TLD中声明属性,是这样吗?
答:这个问题有点意思,如果把标记文件部署在Jar中,就必须有一个TLD来描述它的位置。但是它并不描述属性、body-content等内容。标记文件的相应TLD项只描述具体标记文件的位置。如下:
建立一个简单标记处理器
有体的简单标记
简单标记的API
简单标记处理器的生命期
标记体使用表达式
上面的${message}
将输出“Wear sunscreen.”。
有动态行数据的标记:迭代执行体
最后*输出结果是:
有属性的简单标记
什么是JspFragment?
JspFragment是表示JSP代码的一个对象。它存在的意义就是让别人调用。换句话说,它要运行并生成输出。如果标记调用了一个简单标记处理器,这个标记的体就会封装在JspFragment对象中,然后再setJspBody()方法发送给标记处理器。
关键是必须记住,JspFragment中不能包含任何脚本元素!也就是说,可以包含模板文本、标准和制定动作,以及EL表达式,但是不能出现scriptlet、声明或脚本表达式。
有一点很棒,因为JspFragment是一个对象,所以可以把这个片段传递给其他辅助对象。这些辅助对象再调用JspFragment的另一个方法getJspContext(),从中得到信息。当然,一旦得到上下文,就可以请求属性。所以getJspContext()实际上是标记体向其他对象提供信息的一个途径。
JspFragment的API:
对于invoke()方法:
SkipPageException:停止处理页面
假设有一个页面调用了标记,而且标记依赖于特定请求属性。下面假设这个标记找不到它需要的属性,它知道如果自己不成功,页面的余下部分就不能正常工作,这个时候你想显示前面部分的内容,后一部分的内容不显示,你该怎么办呢?
这个时候SkipPageException就出来了!如下:
会显示异常出现之前的所有内容,如下:
如果从一个被包含页面调用标记,会发生什么情况?
如图:pageB调用了抛出异常的标记
结果如下:
会发现停止页面B的输出,而不影响A的继续处理。
一个有趣的问题
简单标记要点
- 标记文件使用一个页面来实现标记功能,而标记处理器使用一个Java标记处理器类实现标记功能。
- 标记处理器有两种类型:传统和简单(简单标记和标记文件是JSP 2.0中新增加的)。
- 建立简单标记处理器时,要扩展SimpleTagSupport(这个类实现了SimpleTag接口)。
- 要部署一个标记处理器,必须创建一个TLD,使用
<tag>
元素来描述标记,JSTL和其他定制标记库也使用这个元素描述标记。 - 如果使用一个有体的简单标记,要保证这个标记的TLD
<tag>
没有将<body-content>
声明为empty。然后调用getJspBody().invoke()来处理体。 - 简单标记生命周期:简单标记不会由容器重用,所以每次调用标记时,都会实例化标记处理器,并调用其setJspContext()方法。如果标记本身是从另一个标记中调用,则会调用setParent()方法。如果调用标记时有属性,对于每个属性会调用一个bean式的设置方法(setXxx())。如果调用标记时有体(假设其TLD没有声明它的体为空),则会调用setJspBody()方法。最后,调用doTag()方法,结束时,撤销标记处理器实例。
-
只有调用标记时确实有体,才会调用setJspBody()方法。如果调用标记时没有体,不论是空标记
<my:tag/>
,还是开始和结束标记<my:tag></my:tag>
之间没有内容,都不会调用setJspBody()方法。记住,如果标记有体,TLD必须反映出这一点,而且<body-content>
的值不能是“empty”。 - 简单标记的doTag()方法可以设置标记体使用的一个属性,为此先调用getJspContext().setAttribute(),在调用getJspBody().invoke()。
- doTag()方法声明了一个JspException和一个IOException,所以可以直接写至JspWriter,而无需包装在一个try/catch中。
- 通过在循环中调用体(getJspBody().invoke()),可以迭代处理简单标记的体。
- 如果标记有一个属性,要在TLD中使用
<attribute>
元素声明这个属性,并在标记处理器类中提供一个bean式的设置方法。调用标记时,会在doTag()之前先调用这个设置方法。 - getJspBody()方法返回一个JspFragment,它有两个方法:invoke(java.io.Writer)和getJspContext(),getJspContext()返回一个Jspcontext,标记处理器可以用这个JspContext访问PageContext API(访问隐式变量和作用域属性)。
- 如果向invoke()传入null,会把计算的体写至响应输出,不过,如果你想直接访问体内容,可以传入另一个Writer。
- 如果你希望当前页面停止处理,可以抛出一个SkipPageException。如果调用标记的页面包含在另一个页面中,尽管被包含的页面在抛出异常之后就会停止处理,但外层页面仍会继续。