标准的JSP标记可以调用JavaBeans组件和执行请求(request)的分配,简化JSP页面的开发和维护。JSP技术同样支持封装其他类型的动态功能到客户定制的扩展标记中。扩展标记通常以标记库(tag library)的形式发布,包含一系列相关的标记和这些标记的实现对象。
标记库体系结构
JSP扩展标记能够完成很多任务,例如操作内置对象、处理表单、访问数据库、访问其他企业服务和实现流程控制。JSP扩展标记由精通Java语言和数据库的开发人员创建,供Web应用的设计者使用,他们只关心页面的表现形式。
扩展标记是用户定义的JSP元素。当包含扩展标记的JSP页面转化成servlet时,标记被转换成对标记处理类(tag handler)的操作。Web 容器在执行JSP页面对应的servlet时会调用标记处理类(tag handler)的方法。扩展标记有很多特性:
可以通过传递属性定制
访问JSP页面中的可用对象
改变JSP页面的响应(response)
可以互相通讯
可以嵌套,实现复杂的交互
扩展标记实现原理
JSP2.0定义一种新的Tag 处理方式,直接handler.serJspContext(),然后直接执行doTag()方法。
这一节介绍了如何在JSP页面中使用扩展标记和扩展标记的类型。
在使用标记库中的标记之前,开发人员需要在JSP页面中使用taglib伪指令声明标记库:
<%@ taglib uri="myTagLib" prefix="mt" %>
uri属性指向唯一标识标记库描述符的URI。JSP引擎通过匹配uri属性与Web引用部署描述(web.xml)中定义的<taglib-uri>元素来尝试查找标记库描述符。例如,上述taglib指令中的myTaglib将引用在如下Web应用部署描述中的标记库描述符:
<taglib> <taglib-uri>myTagLib</taglib-uri> <taglib-location>/WEB-INF/myTagLib.tld</taglib-location> </taglib>
prefix属性定义了标记库的前缀,用来和其他标记库进行区分,当使用JSP扩展标记编写页面时使用此前缀引用标记库中的标记。例如,如上例中名为myTagLib的库定义了一个名为helloTag的标记,可在JSP页面中这样引用:
<mt:helloTag>
JSP扩展标记使用XML语法,具有开始标记和结束标记,可能还包含标记体:
<tt:tag> body </tt:tag>
没有标记体的标记可以表示为:
<tt:tag />
简单标记
简单标记不包含标记体和属性:
<tt:simple />
带属性的标记
扩展标记可能带有属性。属性被列在开始标记中,语法形式为:attr="value"。属性值用来定制扩展标记的行为,就象参数定义方法的行为一样。开发人员可以在标记库描述符中指定标记属性的类型。属性值来自字符串(String)常量和表达式,由于这两种类型都是字符串(String),那么它们转换为正确属性类型的规则遵循在第 43.1.3.6.3 节 “设置JavaBeans的属性”中的描述。
带有标记体的标记
扩展标记在开始标记和结束标记之间可以包含扩展和标准标记、脚本元素、HTML代码等标记体。
定义脚本变量的标记
扩展标记可以定义在JSP页面中使用的脚本变量。下面的代码片断示例了如何定义和使用脚本变量,脚本变量包含了从JNDI返回的对象:
<mt:lookup id="tx" type="UserTransaction" name="java:comp/UserTransaction" /> <% tx.begin(); %>
协作标记
扩展标记可以通过共享对象互相协作。下面的例子tag1创建了对象obj1,然后在tag2中使用。
<tt:tag1 attr1="obj1" value1="value" /> <tt:tag2 attr1="obj1" />
下一个例子,对象由一组嵌套标记中的封装标记创建,在所有内部标记中也可用。由于对象是没有命名的,可以减少潜在的命名冲突。
<tt:outerTag> <tt:innerTag /> </tt:outerTag>
定义扩展标记需要:
开发标记处理类(tag handler )和辅助类
在标记库描述符( tag library descriptor )中声明标记
这一节描述了标记处理类和标记库描述符的属性,说明如何开发标记处理类和标记库描述符。
标记库描述符(tag library descriptor,TLD)是描述标记库的XML文档,包含了关于标记库的整体信息和其中每一个标记的信息。Web容器使用标记库描述符校验标记。标记库描述符必须以.tld为文件扩展名,存放在WEB-INF目录或其子目录中。
标记库描述符必须以XML文档的序言开始,定义XML和DTD的版本信息。
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
也可在根元素指定(JSP2.0,jsptaglibrary 2.0起支持)
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" version="2.0"
标记库描述符的根元素是taglib ,taglib的子元素为:
元素 | 描述 |
tlib-version | 标记库的版本 |
jsp-version | JSP 规范的版本 |
short-name | 可选 |
uri | 唯一标识标记库的 URI |
display-name | 可选 |
small-icon | 可选,小图标 |
large-icon | 可选,大图标 |
description | 可选,描述信息 |
listener | 应用事件监听类 |
tag | 标记 |
标记库描述符中的每一个tag元素描述了标记的名称、处理类、标记创建的脚本变量和标记的属性。tag元素的子元素为:
元素 | 描述 |
name | 标记名 |
tag-class | 标记处理类 |
tei-class | 可选,javax.servlet.jsp.tagext.TagExtraInfo 子类 |
body-content | 标记体类型 |
display-name | 可选 |
small-icon | 可选,小图标 |
large-icon | 可选,大图标 |
description | 可选,描述信息 |
variable | 可选,脚本变量信息 |
attribute | 标记属性信息 |
例如,下面定义了helloTag标记,包含parameter属性。
<tag> <name>helloTag</name> <tag-class>com.apusic.test.MyTagHandler</tag-class> <body-content>JSP</body-content> ... <attribute> <name>parameter</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> ... </tag>
标记处理类(Tag Handlers )是在 处理包含扩展标记的JSP页面时由Web容器调用。标记处理类必须实现Tag或BodyTag接口,开发人员可以使用TagSupport和BodyTagSupport作为基类,这些类和接口都包含在javax.servlet.jsp.tagext包中。
标记处理类的方法定义在Tag和BodyTag接口中,被JSP页面的实现servlet在不同的地方调用。当遇到开始标记时,JSP页面的实现servlet调用标记处理类适当的方法初始化,然后调用doStartTag方法。当遇到结束标记时,标记处理类的doEndTag方法被调用。如果标记处理类需要和标记体交互,那么附加的方法将在开始和结束标记之间被调用。请参考第 43.3.4 节 “标记处理类如何被调用”。为了提供标记处理类的实现,开发人员必须针对不同的情况实现接口的方法,下面总结了标记处理类的实现方法。
标记类型 | 实现方法 |
简单标记 | doStartTag, doEndTag, release |
带属性的标记 | doStartTag, doEndTag, set/getAttribute1...N, release |
带标记体,需要执行,但不交互 | doStartTag, doEndTag, release |
带标记体,需要反复执行 | doStartTag, doAfterBody, doEndTag, release |
带标记体,需要交互 | doStartTag, doEndTag, release, doInitBody, doAfterBody, release |
标记处理类能够访问与JSP页面通讯的API。访问这些API的入口点是page context对象(javax.servlet.jsp.PageContext),通过PageContext标记处理类能够访问其他JSP页面可用的内置对象:request、session和application。
如果标记是嵌套的,那么内部标记的处理类可以访问封装标记的处理类。
简单标记的处理类必须实现Tag接口的doStartTag和doEndTag方法。当遇到开始标记时,doStartTag方法被调用。因为简单标记没有标记体,所以doStartTag方法返回SKIP_BODY。当遇到结束标记时,doEndTag方法被调用。如果需要执行页面后面的内容,doEndTag返回 EVAL_PAGE,否则返回SKIP_PAGE。例如一个简单标记
<tt:simple />
该标记的处理类为:
public SimpleTag extends TagSupport { //遇到开始标记时调用 public int doStartTag() throws JspException { try { pageContext.getOut().print("Hello."); } catch (Exception ex) { throw new JspTagException("SimpleTag: " + ex.getMessage()); } return SKIP_BODY; } //遇到结束标记时调用 public int doEndTag() { return EVAL_PAGE; } }
简单标记不包含标记体,在标记库描述符中body-content必须声明为empty :
<body-content>empty</body-content>
对标记的每一个属性,开发人员必须遵循JavaBeans体系结构的设计惯例定义get和set方法。例如标记present,
<logic:present parameter="Clear">
包含属性parameter , 必须定义get和set方法,
protected String parameter = null; public String getParameter() { return (this.parameter); } public void setParameter(String parameter) { this.parameter = parameter; }
![]() | 注意 |
---|---|
如果属性命名为id,且标记处理类从TagSupport类继承,那么不需要定义get和set方法。TagSupport类中已经定义了getId和setId。 |
对标记的每一个属性,开发人员必须确定属性是否是必须的,属性值是否可以通过动态的表达式的值得到。通过attribute元素指定属性的类型。对于静态的属性值,属性的类型总是java.lang.String。如果rtexprvalue元素是true或yes,属性类型可以定义为期待表达式返回的类型。
<attribute> <name>attr1</name> <required>true|false|yes|no</required> <rtexprvalue>true|false|yes|no</rtexprvalue> <type>fully_qualified_type</type> </attribute>
如果标记属性不是必须的,标记的处理类应该提供缺省值。
传递给标记的属性可以在JSP页面转换的时候被TagExtraInfo类的isValid方法验证。isValid方法的参数为TagData对象,包含了每个标记的属性-值信息。由于验证发生在JSP页面的转换阶段,需要在请求(request)阶段计算的属性值将被设置为TagData.REQUEST_TIME_VALUE。
标记 <tt:twa attr1="value1"/> 在标记库包含下面的属性声明:
<attribute> <name>attr1</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute>
这段声明指出了属性attr1的值可以在运行时刻决定。下面的isValid()方法检查了属性attr1的值是否为Boolean类型。由于属性attr1的值可以在运行时刻决定,isValid()必须检查标记的使用者是否提供了运行时刻的值。
public class TwaTEI extends TagExtraInfo { //检验属性值 public boolean isValid(Tagdata data) { Object o = data.getAttribute("attr1"); if (o != null && o != TagData.REQUEST_TIME_VALUE) { if (o.toLowerCase().equals("true") || o.toLowerCase().equals("false") ) return true; else return false; } else return true; } }
处理带有标记体的标记处理类有两种不同的实现方式,实现方式的选择取决于是否需要和标记体交互。需要交互意味着标记处理类可以读取和更改标记体的内容。
标记处理类不与标记体交互
如果不需要和标记体交互,标记处理类应该实现Tag接口或继承TagSupport类。doStartTag返回EVAL_BODY_INCLUDE,代表标记体需要执行,否则返回SKIP_BODY。
如果标记处理类需要反复的执行标记体,应该实现IterationTag接口或继承TagSupport类。doStartTag和doAfterBody方法返回EVAL_BODY_AGAIN,确定标记体需要再次执行。
标记处理类和标记体交互
如果需要和标记体交互,标记处理类必须实现BodyTag接口或继承BodyTagSupport类。标记处理类一般通过实现doInitBody和doAfterBody方法来与JSP页面的实现servlet传递过来的标记体内容交互。
BodyContent支持几种读取和写入的方法。标记处理类使用BodyContent的getString或getReader方法读取标记体的内容信息,使用writeOut(out)方法向输出流写入标记体内容。
如果标记体需要执行,doStartTag方法返回EVAL_BODY_BUFFERED,否则返回SKIP_BODY。
doInitBody方法在BodyContent设置后、标记体执行之前被调用。通常用来完成依赖于标记体内容的初始化操作。
doAfterBody方法在标记体执行后被调用。象doStartTag一样,doAfterBody必须返回指示,确定是否需要继续执行标记体。如果需要再次执行标记体,返回EVAL_BODY_BUFFERED,否则doAfterBody返回SKIP_BODY。
标记处理类在release方法中重新设置状态,释放所有资源。
类型 | 概览 |
处理类不与标记体交互 |
|
处理类和标记体交互 |
|
下面的例子读取了标记体的内容,然后传递给对象作为SQL语句执行。因为标记体不需要再次执行,doAfterBody返回SKIP_BODY。
public class QueryTag extends BodyTagSupport { public int doAfterBody() throws JspTagException { BodyContent bc = getBodyContent(); // 得到标记体内容 String query = bc.getString(); // 清除 bc.clearBody(); try { //作为 SQL 语句执行 Statement stmt = connection.createStatement(); result = stmt.executeQuery(query); } catch (SQLException e) { throw new JspTagException("QueryTag: " + e.getMessage()); } return SKIP_BODY; } }
对于包含标记体的标记,开发人员必须在body-content指定标记体的类型。
<body-content>JSP|tagdependent</body-content>
包含扩展和标准标记、脚本元素、HTML文本的标记体归类为JSP,所有的其他类型归类为tagdependent 。
标记处理类能够创建和设置对象,然后通过页面运行环境(context)关联到脚本变量。使用pageContext.setAttribute(name,value, scope) 或pageContext.setAttribute(name, value)来完成这种关联。一般通过扩展标记的属性传递脚本变量的名称,然后可以调用属性的get方法得到这个名称。通常的处理过程是标记处理类得到脚本变量,执行一些处理,然后使用pageContext.setAttribute(name,object) 设置脚本变量值。
JSP页面包含了能够定义脚本变量的标记,在它转换成servlet的阶段,Web容器产生定义脚本变量、设置脚本变量到对象引用的代码。为了产生这些代码,Web容器需要确定脚本变量的信息:
变量名
变量类型
变量引用新的对象或已存在的对象
变量的可用范围
有两种方式可以提供这些信息:
在标记库描述符中定义variable元素
定义标记扩展信息类(TagExtraInfo )并在标记库描述符中定义tei-class元素
variable元素使用简单,但缺少一些弹性。variable元素包含下面的子元素:
name-given:给定常量作为变量名
name-from-attribute:属性的名称,属性值作为变量名
name-given和name-from-attribute之中一个是必须的。其他子元素是可选的:
variable-class:变量的类型,缺省是java.lang.String
declare:变量是否关联到新的对象,缺省是true
scope:变量的作用范围,缺省是NESTED
下面列出所有的变量作用范围。
值 | 可用性 | 方法 |
NESTED | 开始和结束标记之间 | doInitBody 、doAfterBody 、doStartTag |
AT_BEGIN | 从开始标记到页面结束 | doInitBody、doAfterBody、doEndTag 、doStartTag |
AT_END | 从结束标记到页面结束 | doEndTag |
开发人员可以继承javax.servlet.jsp.TagExtraInfo定义标记的扩展信息类。TagExtraInfo必须实现getVariableInfo方法,返回VariableInfo对象数组,包含了确定脚本变量的所有信息。例如下面的代码:
public class DefineTei extends TagExtraInfo { public VariableInfo[] getVariableInfo(TagData data) { String type = data.getAttributeString("type"); if (type == null) type = "java.lang.Object"; return new VariableInfo[] { new VariableInfo(data.getAttributeString("id"), type, true, VariableInfo.AT_BEGIN) }; } }
VariableInfo对象构造函数的参数为:脚本变量名称、类型、是否为新的变量和变量的作用范围。另外,需要在标记库描述符中定义tei-class元素指向扩展信息类:
<tei-class>com.apusic.test.DefineTagTei</tei-class>