JSP页面中的自定义标签2

80酷酷网    80kuku.com

  js|页面JSP页面中的自定义标签2




带属性的标签
在标签handler中定义属性
对于每一个标签属性,都必须在标签handler中定义一个属性以及符合JavaBean结构规范的get和set方法。例如,logic:present标签的标签handler

<logic:present parameter="Clear">

包含下列声明和方法:

protected String parameter = null;
public String getParameter() {
return (this.parameter);
}
public void setParameter(String parameter) {
this.parameter = parameter;
}


--------------------------------------------------------------------------------

注意,如果属性命名为id并且标签handler继承自TagSupport类,那么就不需要定义属性和set和get方法,因为它们已经由TagSupport定义了。


--------------------------------------------------------------------------------

值为String的标签属性可以指定标签handler可用的隐式对象的一个属性。通过向隐式对象的[set|get]Attribute方法传递标签属性值可以访问一个隐式对象属性。这是将脚本变量名传递给标签handler的好方式,在这里脚本变量与储存在页面上下文中的对象相关联(见隐式对象)。

attribute元素
对于每一个标签属性,都必须在attribute元素中指定这个属性是否是必需的、其值是否可以由表达式确定、还可能指定属性的类型。对于静态值,类型总是java.lang.String。如果rtexprvalue元素是true或者yes,那么type元素定义会将任何指定的表达式的预期返回类型指定为属性的值。

<attribute>
<name>attr1</name>
<required>true|false|yes|no</required>
<rtexprvalue>true|false|yes|no</rtexprvalue>
<type>fully_qualified_type</type>
</attribute>

如果tag属性不是必需的,那么标签handler应该提供一个默认值。

logic:present标签的tag元素声明parameter属性不是必需的(因为标签还可以测试是否存在其它实体,如bean属性)以及其值可以由运行时表达式设置。

<tag>
<name>present</name>
<tag-class>org.apache.struts.taglib.
logic.PresentTag</tag-class>
<body-content>JSP</body-content>
...
<attribute>
<name>parameter</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
...
</tag>

属性验证
标签库的文档应该描述标签属性的有效值。在转换JSP页面时,Web容器将强制应用每一个属性的TLD元素中包含的限制。

在转换时还用从TagExtraInfo派生的类的isValid方法验证传递给标签的属性。这个类也用于提供有关标签定义的脚本变量的信息(见提供有关脚本变量的信息)。

用TagData对象向isValid方法传递属性信息,它包含每一个标签属性的属性-值元组。因为验证在转换时发生,所以在请求时计算的属性值将设置为TagData.REQUEST_TIME_VALUE。

<tt:twa attr1="value1"/>标签有下列TLD attribute元素:

<attribute>
<name>attr1</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>

这个声明表明attr1的值可以在运行时确定。

下面的isValid方法检查attr1的值是否为有效的布尔值。注意由于的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 (((String)o).toLowerCase().equals("true") ||
((String)o).toLowerCase().equals("false") )
return true;
else
return false;
}
else
return true; }
}

带正文的标签
标签handler
带正文的标签的标签handler根据标签handler是否需要与正文交互而有不同的实现。我们说的交互的意思是标签handler读取或者修改正文的内容。

标签handler不与正文交互
如果标签handler不需要与正文交互,那么标签handler应该实现Tag接口(或者从TagSupport派生)。如果需要对标签的正文进行判断,那么doStartTag方法就需要返回EVAL_BODY_INCLUDE,否则,它应该返回SKIP_BODY。

如果标签handler需要反复地判断正文,那么它就应该实现IterationTag接口或者从TagSupport派生。如果它确定需要再次评估正文,那么它应该从doStartTag和doAfterBody方法返回EVAL_BODY_AGAIN。

标签handler与正文交互
如果标签handler需要与正文交互,那么标签handler必须实现BodyTag (或者从BodyTagSupport派生)。这种处理器通常实现doInitBody和doAfterBody方法。这些方法与由JSP页面的servlet传递给tag handler的正文内容交互。

正文内容支持几种读取和写入其内容的方法。标签handler可以用正文内容的getString或者getReader方法从正文中提取信息,用writeOut(out)方法将正文内容写入一个输出流。为writeOut方法提供的writer是用标签handler的getPreviousOut方法得到的。用这个方法保证标签handler的结果对于其外围标签handler是可用的。

如果需要对标签的正文进行判断,那么doStartTag方法需要返回EVAL_BODY_BUFFERED,否则它就应该返回SKIP_BODY。

doInitBody 方法

在已经设置正文内容之后、但是对它进行判断之前调用doInitBody方法。一般用这个方法执行所有依赖于正文内容的初始化。

doAfterBody方法

doAfterBody方法在判断了正文内容之后调用。

像doStartTag方法一样,doAfterBody必须返回指明是否继续判断正文的指示。因此,如果应该再次判断正文,就像实现枚举标签的情况,那么doAfterBody应该返回EVAL_BODY_BUFFERED,否则doAfterBody应该返回SKIP_BODY。

Release方法

标签handler应该在release方法中重新设置其状态并释放所有私有资源。

下面的例子读取正文的内容(它包含一个SQL查询)并将它传递给一个执行这个查询的对象。因为不需要对正文再次判断,所以doAfterBody返回SKIP_BODY。

public class QueryTag extends BodyTagSupport {
public int doAfterBody()
throws JspTagException {
BodyContent bc = getBodyContent();
// get the bc as string
String query = bc.getString();
// clean up bc.clearBody();
try {
Statement stmt = connection.createStatement();
result = stmt.executeQuery(query);
} catch (SQLException e) {
throw new JspTagException("QueryTag: " +
e.getMessage());
}
return SKIP_BODY;
}
}

body-content元素
对于有正文的标签,必须用body-content元素指定正文内容的类型:

<body-content>JSP|tagdependent</body-content>

正文内容包含自定义和核心标签、脚本元素以及属于JSP的HTML文字。这是为Struts logic:present标签声明的值。所有其它类型的正文内容——如传递给查询标签的SQL语句,都标记为tagdependent。

注意body-content元素的值不影响标签handler对正文的解读,这个元素只是由编写工具用于呈现正文内容。

定义脚本变量的标签
标签handler
标签handler负责创建脚本变量引用的对象并设置到页面可以访问的上下文中。它是用pageContext.setAttribute(name, value, scope)或者pageContext.setAttribute(name, value)方法完成这项工作的。通常传递给自定义标签的属性指定脚本变量对象的名字,通过调用在使用范围对象中描述的属性的get方法可以提取这个名字。

如果脚本变量的值依赖于在标签handler上下文中出现的一个对象,那么它可以用pageContext.getAttribute(name, scope)方法提取这个对象。

一般的通常过程是标签handler提取脚本变量、对对象执行一些处理、再用pageContext.setAttribute(name, object)方法设置脚本变量的值。

表16-4总结了对象可以有的作用域。作用域限制了对象的可访问性和寿命。

表16-4 对象范围

名字
可访问性
寿命

page
当前页面
直到响应被返回到用户或者请求被传递给一个新页面

request
当前页面及所有包含或者转发页面
直到响应被返回到用户

session
当前请求和所有从同一个浏览器发出的后续请求(取决于会话寿命)
用户会话的寿命

application
同一Web应用程序的当前和所有未来请求
应用程序的寿命



提供有关脚本变量的信息
在定义脚本变量的标签中描述的例子定义了用于访问图书信息的脚本变量:book

<bean:define id="book"
name="bookDB" property="bookDetails"
type="database.BookDetails"/>
<font color="red" size="+2">
<%=messages.getString("CartRemoved")%>
<strong><jsp:getProperty name="book"
property="title"/></strong>

 

</font>

在转换包含这个标签的JSP页面时,Web容器会生成同步脚本变量与由变量引用的对象的代码。要生成这些代码,Web容器需要关于脚本变量的一些信息:

· 变量名

· 变量类

· 变量是否引用新的对象或者是现有对象

· 变量的可用性

有两种方法提供这种信息:指定variable TLD子元素或者定义tag extra info类并在TLD中包含tei-class元素。用variable元素更简单,但是灵活性要差一些。

variable 元素
variable元素有下列子元素:

· name-given:变量名为常量

· name-from-attribute:一个属性的名字,其转换时(translation-time)值将给出属性的名字

必须有name-given或者name-from-attribute之中的一个。下列子元素是可选的:

· variable-class—变量的完全限定名。默认为java.lang.String。

· declare—变量是否引用新对象。默认为True。

· scope—定义的脚本变量的作用域。默认为NESTED。表16-5描述了脚本变量的可用性以及必须设置或者重新设置变量值的方法。

表 16-5 脚本变量可用性


可用性
方法

NESTED
开始和结束标签之间
在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中,否则,在 doStartTag中

AT_BEGIN
从开始标签到页面的结束
在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中,否则,在 doStartTag和doEndTag中

AT_END
在结束标签之后直到页面的结束
在doEndTag中


Struts bean:define标签的实现符合JSP规范版本1.1,它要求定义tag extra info类。JSP规范版本1.2增加了variable元素。可以为bean:define标签定义下面的variable元素:

<tag>
<variable>
<name-from-attribute>id</name-from-attribute>
<variable-class>database.BookDetails</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag>

TagExtraInfo类
通过扩展类javax.servlet.jsp.TagExtraInfo定义tag extra info类。TagExtraInfo. A TagExtraInfo必须实现getVariableInfo方法以返回包含下列信息的VariableInfo对象数组:

· 变量名

· 变量类

· 变量是否引用新对象

· 变量可用性

Web容器向getVariableInfo方法传递包含每一个标签属性的属性-值元组的名为data的参数。这些属性可以用于为VariableInfo对象提供脚本变量名和类。

Struts标签库提供有关由DefineTei tag extra info类中的bean:define标签创建的脚本变量的信息。由于脚本变量的name (book)和class (database.BookDetails)作为标签属性传递,所以可以用data.getAttributeString方法提取它们,并用于填充VariableInfo构造函数。要使脚本变量book用于页面的其他地方,book的作用域设置为AT_BEGIN。

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)
};
}
}

为脚本变量定义的tag extra info类的完全限定名必须在tag元素的tei-class子元素的TLD中声明。因此,DefineTei的tei-class元素像下面这样:

<tei-class>
org.apache.struts.taglib.bean.DefineTagTei
</tei-class>

标签协同操作
标签通过共享对象实现合作。JSP技术支持两种类型的对象共享。

第一种类型要求在页面上下文中命名和储存共享的对象(JSP页面和标签handler都可以访问的一种隐式对象)。要访问由另一个标签创建和命名的对象,标签handler使用pageContext.getAttribute(name, scope)方法。

在第二种对象共享类型中,由一组嵌入标签中的外围标签handler创建的对象可以被所有内部标签handler访问。这种形式的对象共享的优点是它对对象使用私有命名空间,因此减少了潜在的命名冲突。

要访问由外围标签创建的对象,标签handler必须首先用静态方法TagSupport.findAncestorWithClass(from, class)或者TagSupport.getParent方法获得其外围标签。在不能保证有特定的嵌入标签handler时应该使用前一个方法。一旦获取了上级,那么标签handler就可以访问所有静态或动态创建的对象了。静态创建的对象是父标签的成员。私有对象也可以动态创建。这种对象可以用setValue方法储存在标签 handler中,并用getValue方法获取它。

下面的例子展示了同时支持命名的和私有对象方式共享对象的标签handler。在这个例子中,查询标签的handler检查名为connection的属性是否已在doStartTag方法中设置。如果属性已经设置,那么handler就从页面上下文中获取连接对象。否则,标签handler首先获取外围标签的标签handler,然后从那个handler中获取连接对象。

public class QueryTag extends BodyTagSupport {
private String connectionId;
public int doStartTag()
throws JspException {
String cid = getConnection();
if (cid != null) {
// there is a connection id, use it
connection =(Connection)pageContext.
getAttribute(cid);
} else {
ConnectionTag ancestorTag =
(ConnectionTag)findAncestorWithClass(this,
ConnectionTag.class);
if (ancestorTag == null) {
throw new JspTagException("A query without
a connection attribute must be nested
within a connection tag.");
}
connection = ancestorTag.getConnection();
}
}
}

由这个标签 handler实现的查询标签可以以下面任何一种方式使用:

<tt:connection id="con01" ....>
...
</tt:connection>
<tt:query id="balances" connection="con01">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</tt:query>
<tt:connection ...>
<x:query id="balances">
SELECT account, balance FROM acct_table
where customer_number = <%= request.getCustno()%>
</x:query>
</tt:connection>

标签handler的TLD必须用下面声明指明connection属性是可选的:

<tag>
...
<attribute>
<name>connection</name>
<required>false</required>
</attribute>
</tag>

示例
本节中描述的自定义标签展示了在开发JSP应用程序时会经常遇到的两个问题的解决方法:尽可能减少JSP页面中的Java编程以及保证整个应用程序的共同外观。在这个过程中,展示了本章前面讨论过的许多类型的标签。

迭代(Iteration)标签
构建依赖于动态生成的数据的页面内容通常需要使用流控制脚本语句。通过将流控制逻辑转换到标签handler中,流控制标签减少了在JSP页面中需要的脚本量。

Struts logic:iterate标签从储存在JavaBeans组件中的集合中获取对象并将它们指定给脚本变量。标签的正文从脚本变量中提取信息。如果集合中仍有元素,则iterate标签会再次对正文进行判断。

JSP页面
两个Duke's Bookstore应用程序页面catalog.jsp和showcart.jsp使用了logic:iterate标签以迭代对象的集合。下面展示了catalog.jsp的一部分。JSP页面用bookDB bean集合(由property属性命名)初始化iterate标签。iterate标签在对集合上的每一次迭代中设置book脚本变量。book变量的bookId属性作为另一个脚本变量公开。两个变量的属性都用于动态生成一个包含到其他页面的图书目录信息的链接的表。

<logic:iterate name="bookDB" property="books"
id="book" type="database.BookDetails">
<bean:define id="bookId" name="book" property="bookId"
type="java.lang.String"/>

<tr>
<td bgcolor="#ffffaa">
<a
/banner.jsp
/bookdetails.jsp

/showcart
Shopping Cart
/banner.jsp
/showcart.jsp

/cashier
Cashier
/banner.jsp
/cashier.jsp

/receipt
Receipt
/banner.jsp
/receipt.jsp


.

在doEndTag中,DefinitionTag创建Definition类的一个公共对象,根据在请求中传递的URL从screens对象中选择一个屏幕定义,并用它初始化Definition对象。

public int doEndTag()throws JspTagException {
try {
Definition definition = new Definition();
HashMap screens = null;
ArrayList params = null;
TagSupport screen = null;
screens = (HashMap)
pageContext.getAttribute("screens",
pageContext.APPLICATION_SCOPE);
if (screens != null)
params = (ArrayList) screens.get(screenId);
else
...
if (params == null)
...
Iterator ir = null;
if (params != null)
ir = params.iterator();
while ((ir != null) && ir.hasNext())
definition.setParam((Parameter) ir.next());
// put the definition in the page context
pageContext.setAttribute(
definitionName, definition);
} catch (Exception ex) {
ex.printStackTrace();
} return EVAL_PAGE;
}

如果在请求中传递的URL是/enter,那么Definition包含表16-6中第一行的项目:

标题
横幅
正文

Duke's Bookstore
/banner.jsp
/bookstore.jsp


URL /enter的定义如表16-7所示。这个定义指定Title参数的值Duke's Bookstore应该直接插队入到输出流中,但是Banner和Body的值应该动态地加入。

表16-7 URL /enter 的屏幕定义

参数名
参数值
isDirect

title
Duke's Bookstore
true

banner
/banner.jsp
false

body
/bookstore.jsp
false


InsertTag使用Definition将屏幕定义的参数插入响应中。在doStartTag方法中,它从页面上下文中获取定义对象。

public int doStartTag() {
// get the definition from the page context
definition = (Definition) pageContext.
getAttribute(definitionName);
// get the parameter
if (parameterName != null && definition != null)
parameter = (Parameter)definition.
getParam(parameterName);
if (parameter != null)
directInclude = parameter.isDirect();
return SKIP_BODY;
}

doEndTag方法插入参数值。如果参数是直接的,那么就直接将它插入响应中,否则,请求就被发送给参数,而其响应则被动态地包含进整个响应中。

public int doEndTag()throws JspTagException {
try {
if (directInclude && parameter != null)
pageContext.getOut().print(parameter.getValue());
else {
if ((parameter != null) &&
(parameter.getValue() != null))
pageContext.include(parameter.getValue());
}
} catch (Exception ex) {
throw new JspTagException(ex.getMessage());
}
return EVAL_PAGE;
}

如何调用标签handler?
Tag接口定义了标签handler与JSP页面的servlet之间的基本协议。它定义了生命周期以及在遇到开始和结束标签时要调用的方法。

JSP页面的servlet在调用doStartTag之前调用setPageContext、setParent和属性设置方法。JSP页面的servlet还保证在结束页面之前调用标签handler的release。

下面是典型的标签handler方法调用顺序:

A Tag t = new ATag();
t.setPageContext(...);
t.setParent(...);
t.setAttribute1(value1);
t.setAttribute2(value2);
t.doStartTag();
t.doEndTag();
t.release();

BodyTag接口通过定义让标签handler访问其正文的其他方法扩展Tag。这个接口提供三个新方法:

· setBodyContent—创建正文内容并添加给tag handler

· doInitBody—在评估标签正文之前调用

· doAfterBody—在评估标签正文之后调用

典型的调用顺序为:

t.doStartTag();
out = pageContext.pushBody();
t.setBodyContent(out);
// perform any initialization needed after body content is set t.doInitBody();
t.doAfterBody();
// while doAfterBody returns EVAL_BODY_BUFFERED we
// iterate body evaluation
...
t.doAfterBody();
t.doEndTag();
t.pageContext.popBody();
t.release();




分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: