自定义jsp标记库
1. 基本步骤 实例-输出“hello,eking” public class HelloTag extends TagSupport { 3.标记处理类 4.自定义标记的生命周期 下面两个方法只有在继承BodyTagSupport类的时候才需要重载 4.带属性的标记-从页面向自定义标记传递信息 对于每一个标签属性,都必须在标签handler中定义一个属性以及符合JavaBean结构规范的get和set方法。例如,logic:present标签的标签handler protected String parameter = null; 注意,如果属性命名为id并且标签handler继承自TagSupport类,那么就不需要定义属性和set和get方法,因为它们已经由TagSupport定义了。 值为String的标签属性可以指定标签handler可用的隐式对象的一个属性。通过向隐式对象的[set|get]Attribute方法传递标签属性值可以访问一个隐式对象属性。这是将脚本变量名传递给标签handler的好方式,在这里脚本变量与储存在页面上下文中的对象相关联(见隐式对象)。 对于每一个标签属性,都必须在attribute元素中指定这个属性是否是必需的、其值是否可以由表达式确定、还可能指定属性的类型。对于静态值,类型总是java.lang.String。如果rtexprvalue元素是true或者yes,那么type元素定义会将任何指定的表达式的预期返回类型指定为属性的值。 <attribute> 如果tag属性不是必需的,那么标签handler应该提供一个默认值。 logic:present标签的tag元素声明parameter属性不是必需的(因为标签还可以测试是否存在其它实体,如bean属性)以及其值可以由运行时表达式设置。 <tag> 标签库的文档应该描述标签属性的有效值。在转换JSP页面时,Web容器将强制应用每一个属性的TLD元素中包含的限制。 在转换时还用从TagExtraInfo派生的类的isValid方法验证传递给标签的属性。这个类也用于提供有关标签定义的脚本变量的信息(见提供有关脚本变量的信息)。 用TagData对象向isValid方法传递属性信息,它包含每一个标签属性的属性-值元组。因为验证在转换时发生,所以在请求时计算的属性值将设置为TagData.REQUEST_TIME_VALUE。 <tt:twa attr1="value1"/>标签有下列TLD attribute元素: <attribute> 下面的isValid方法检查attr1的值是否为有效的布尔值。注意由于的attr1值可以在运行时计算,所以isValid必须检查标签用户是否选择了提供运行时值。 public class TwaTEI extends TagExtraInfo { 带正文的标签的标签handler根据标签handler是否需要与正文交互而有不同的实现。我们说的交互的意思是标签handler读取或者修改正文的内容。 如果标签handler不需要与正文交互,那么标签handler应该实现Tag接口(或者从TagSupport派生)。如果需要对标签的正文进行判断,那么doStartTag方法就需要返回EVAL_BODY_INCLUDE,否则,它应该返回SKIP_BODY。 如果标签handler需要反复地判断正文,那么它就应该实现IterationTag接口或者从TagSupport派生。如果它确定需要再次评估正文,那么它应该从doStartTag和doAfterBody方法返回EVAL_BODY_AGAIN。 如果标签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方法。一般用这个方法执行所有依赖于正文内容的初始化。 像doStartTag方法一样,doAfterBody必须返回指明是否继续判断正文的指示。因此,如果应该再次判断正文,就像实现枚举标签的情况,那么doAfterBody应该返回EVAL_BODY_BUFFERED,否则doAfterBody应该返回SKIP_BODY。 标签handler应该在release方法中重新设置其状态并释放所有私有资源。 下面的例子读取正文的内容(它包含一个SQL查询)并将它传递给一个执行这个查询的对象。因为不需要对正文再次判断,所以doAfterBody返回SKIP_BODY。 public class QueryTag extends BodyTagSupport { 对于有正文的标签,必须用body-content元素指定正文内容的类型: 正文内容包含自定义和核心标签、脚本元素以及属于JSP的HTML文字。这是为Struts logic:present标签声明的值。所有其它类型的正文内容——如传递给查询标签的SQL语句,都标记为tagdependent。 注意body-content元素的值不影响标签handler对正文的解读,这个元素只是由编写工具用于呈现正文内容。 标签handler负责创建脚本变量引用的对象并设置到页面可以访问的上下文中。它是用pageContext.setAttribute(name, value, scope)或者pageContext.setAttribute(name, value)方法完成这项工作的。通常传递给自定义标签的属性指定脚本变量对象的名字,通过调用在使用范围对象中描述的属性的get方法可以提取这个名字。 如果脚本变量的值依赖于在标签handler上下文中出现的一个对象,那么它可以用pageContext.getAttribute(name, scope)方法提取这个对象。 一般的通常过程是标签handler提取脚本变量、对对象执行一些处理、再用pageContext.setAttribute(name, object)方法设置脚本变量的值。 表16-4总结了对象可以有的作用域。作用域限制了对象的可访问性和寿命。 在定义脚本变量的标签中描述的例子定义了用于访问图书信息的脚本变量:book <bean:define id="book" 在转换包含这个标签的JSP页面时,Web容器会生成同步脚本变量与由变量引用的对象的代码。要生成这些代码,Web容器需要关于脚本变量的一些信息: 有两种方法提供这种信息:指定variable TLD子元素或者定义tag extra info类并在TLD中包含tei-class元素。用variable元素更简单,但是灵活性要差一些。 · name-from-attribute:一个属性的名字,其转换时(translation-time)值将给出属性的名字 必须有name-given或者name-from-attribute之中的一个。下列子元素是可选的: · variable-class—变量的完全限定名。默认为java.lang.String。 · scope—定义的脚本变量的作用域。默认为NESTED。表16-5描述了脚本变量的可用性以及必须设置或者重新设置变量值的方法。 在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中,否则,在 doStartTag中 在实现BodyTag的标签handler的doInitBody 和doAfterBody方法中,否则,在 doStartTag和doEndTag中 Struts bean:define标签的实现符合JSP规范版本1.1,它要求定义tag extra info类。JSP规范版本1.2增加了variable元素。可以为bean:define标签定义下面的variable元素: <tag> 通过扩展类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 { 为脚本变量定义的tag extra info类的完全限定名必须在tag元素的tei-class子元素的TLD中声明。因此,DefineTei的tei-class元素像下面这样: <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 { 由这个标签 handler实现的查询标签可以以下面任何一种方式使用: <tt:connection id="con01" ....> 标签handler的TLD必须用下面声明指明connection属性是可选的: <tag> 本节中描述的自定义标签展示了在开发JSP应用程序时会经常遇到的两个问题的解决方法:尽可能减少JSP页面中的Java编程以及保证整个应用程序的共同外观。在这个过程中,展示了本章前面讨论过的许多类型的标签。 构建依赖于动态生成的数据的页面内容通常需要使用流控制脚本语句。通过将流控制逻辑转换到标签handler中,流控制标签减少了在JSP页面中需要的脚本量。 Struts logic:iterate标签从储存在JavaBeans组件中的集合中获取对象并将它们指定给脚本变量。标签的正文从脚本变量中提取信息。如果集合中仍有元素,则iterate标签会再次对正文进行判断。 两个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" <tr> <td bgcolor="#ffffaa" rowspan=2> <tr> Struts logic:iterate标签的实现符合JSP版本1.1规范的要求,它需要扩展BodyTagSupport类。JSP版本1.2规范添加了简化迭代性地对正文判断的编程标签的功能(在不与正文交互的标签handler中描述)。下面的讨论是使用这些功能的实现。 logic:iterate标签以几种方式支持集合初始化:用作为标签属性而提供的集合,或者用作为bean或者bean属性集合而提供的集合。我们的例子使用后一种方法。doStartTag中的大多数代码是关于构建对于一个对象集合的迭代器的。方法首先检查是否设置了handler的集合属性,如果没有,则进一步检查bean和property属性。如果name和property属性都设置了,则doStartTag调用使用JavaBean自省方法的工具方法来获取集合。一旦确定了集合对象,方法就构建迭代器。 如果迭代器中还有元素,那么doStartTag就设置脚本变量的值为下一个元素,然后表明要对这个正文进行判断,否则就返回SKIP_BODY结束迭代。 在判断完正文后,doAfterBody方法提取正文内容并将它写入输出流。然后清除正文内容对象以便为另一次正文判断作准备。如果迭代器还包含元素,那么doAfterBody就再次设置脚本变量的值为下一个元素并返回EVAL_BODY_AGAIN以表明应该再次对正文进行判断。这样会再次执行doAfterBody。如果没有剩余元素了,那么就返回SKIP_BODY终止这个过程。 public class IterateTag extends TagSupport { 有关脚本变量的信息是在IterateTei标签额外信息类中提供的。脚本变量的名字和类以标签属性的形式传入,并用于加入VariableInfo构造函数。 public class IterateTei extends TagExtraInfo { return new VariableInfo[] { 模板提供了一种将应用程序中每一屏幕都会出现的共用元素与每一屏幕都会改变的元素分离开来的方法。将所有公共元素一起放到一个文件中更容易进行维护,并可以加强所有屏幕的外观一致性。它还使每一屏幕的开发更容易了,因为开发者只要注重于该屏幕特定的那部分内容就可以了,模板会负责公共部分。 模板是JSP页面,在每一屏幕需要改变的地方有占位符。每一个占位符称为模板的参数。例如,一个简单的模板可能包含在生成的屏幕顶部的一个标题参数和JSP页面的正文参数以设定屏幕的定制内容。 模板使用嵌入的标签——definition、screen和parameter——定义屏幕定义表并使用标签将屏幕定义插入到特定应用程序屏幕。 下面展示Duke's Bookstore例子的模板template.jsp。这一页面包括一个创建屏幕定义、并用insert标签将定义中的参数插入应用程序屏幕的JSP页面。 <%@ taglib uri="/tutorial-template.tld" prefix="tt" %> screendefinitions.jsp根据请求属性selectedScreen创建屏幕定义: <tt:definition name="bookstore" 模板由Dispatcher servlet实例化。Dispatcher首先得到所请求的屏幕并将它储存为请求的属性。这是必要的,因为在向template.jsp转发请求时,请求URL不包含原来的请求(如/bookstore3/catalog),而是反映转发布页面的路径(/bookstore3/template.jsp)。最后,servlet将请求分发给template.jsp: public class Dispatcher extends HttpServlet {
1) 标记处理类
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
public int doEndTag() throws javax.servlet.jsp.JspException {
return Tag.EVAL_PAGE;
}
public void release() {
super.release();
}
public int doStartTag() throws javax.servlet.jsp.JspException {
JspWriter out=pageContext.getOut();
try{
out.println("hello, eking");
}catch(java.io.IOException ioe){
ioe.printStackTrace();
}
return Tag.SKIP_BODY;
}
}
2)标记库描述符 hellodemo.tld文件
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>hellodemo</shortname>
<tag>
<name>hello</name>
<tagclass>WebLayer.JspTags.HelloTag</tagclass>
<bodycontent>empty</bodycontent>
</tag>
</taglib>
3) 在web.xml中加入
<taglib>
<taglib-uri>/WEB-INF/hellodemo.tld</taglib-uri>
<taglib-location>/WEB-INF/hellodemo.tld</taglib-location>
</taglib>
4)hello.jsp
<%@ taglib uri="/WEB-INF/hellodemo.tld" prefix="demo" %>
....
<demo:hello/>
2.TLD描述文件
1)一个taglib下可以有多个tag
2)tag标签下的bodycontent可以为:
empty: 空标记,即起始标记和结束标记之间没有内容
JSP:标记题为jsp文本,将和页面上的jsp文本一起被处理
tagdependent:标记体内容由java标记处理类 来进行处理,不能当作jsp文本
必须实现javax.servlet.jsp.tagext包中接口(Tag,IterationTag,BodyTag)之一。为了简化开发,我们只需要继承包中的两个抽象类,再重载某些方法即可
1)TagSupport: 标记体为空或不需要处理标记体
2)BodyTagSupport: 标记体需要被处理 (对应的bodycontent=tagdependent)
1)doStartTag()
处理起始标记时调用,返回一个int值表明对标记体的处理方式:
Tag.SKIP_BODY:忽略标记体 (对应的bodycontent=empty)
Tag.EVAL_BODY_INCLUDE: 处理标记体
2)doEndTag()
标记体结束时调用,返回一个int值表明对jsp余下页面的处理方式:
Tag.EVAL_PAGE : 继续执行jsp文件的内容
Tag.SKIP_PAGE : 停止执行
3)release()
释放资源
4)doInitBody()
在调用doStartTag()之后,处理标记体之前被调用
5)doAfterBody()
在处理完标记体后,调用doEndTag()之前调用。
该方法的返回值代表对标记体的处理方式:
IterationTag.EVAL_BODY_AGAIN : 需要再次处理标记体
Tag.SKIP_BODY: 结束对标记体的处理
1)页面
<demo:hello name="username" />
属性值也可以是表达式,如 :<demo:hello name="<%=request.getParameter("username")%>" />
2)标签处理类
必须提供get,set方法对属性进行操作
每个属性的set方法会在doStartTag()之前自动被调用
3)TLD文件中必须对所有的属性进行定义
<tag>
...
<attribute>
<name>属性名</name>
<required>true表示属性必须存在,false表示可由可无</required>
<rtexprvalue>定义属性是否允许使用表达式,为false表示只允许使用字符串</rtexprvalue>
</attribute>带属性的标签
在标签handler中定义属性
public String getParameter() {
return (this.parameter);
}
public void setParameter(String parameter) {
this.parameter = parameter;
} attribute元素
<name>attr1</name>
<required>true|false|yes|no</required>
<rtexprvalue>true|false|yes|no</rtexprvalue>
<type>fully_qualified_type</type>
</attribute>
<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> 属性验证
<name>attr1</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
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与正文交互
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元素
定义脚本变量的标签
标签handler
提供有关脚本变量的信息
name="bookDB" property="bookDetails"
type="database.BookDetails"/>
<font color="red" size="+2">
<%=messages.getString("CartRemoved")%>
<strong><jsp:getProperty name="book"
property="title"/></strong>
<br> <br>
</font> variable 元素
<variable>
<name-from-attribute>id</name-from-attribute>
<variable-class>database.BookDetails</variable-class>
<declare>true</declare>
<scope>AT_BEGIN</scope>
</variable>
</tag> 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)
};
}
}
org.apache.struts.taglib.bean.DefineTagTei
</tei-class> 标签协同操作
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();
}
}
}
...
</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>
...
<attribute>
<name>connection</name>
<required>false</required>
</attribute>
</tag> 示例
迭代(Iteration)标签
JSP页面
id="book" type="database.BookDetails">
<bean:define id="bookId" name="book" property="bookId"
type="java.lang.String"/>
<td bgcolor="#ffffaa">
<a href="<%=request.getContextPath()%>
/bookdetails?bookId=<%=bookId%>">
<strong><jsp:getProperty name="book"
property="title"/> </strong></a></td>
<jsp:setProperty name="currency" property="amount"
value="/<%=book.getPrice()%>"/>
<jsp:getProperty name="currency" property="format"/>
</td> <td bgcolor="#ffffaa" rowspan=2>
<a href="<%=request.getContextPath()%>
/catalog?Add=<%=bookId%>">
<%=messages.getString("CartAdd")%>
</a></td></tr>
<td bgcolor="#ffffff">
<%=messages.getString("By")%> <em>
<jsp:getProperty name="book"
property="firstName"/>
<jsp:getProperty name="book"
property="surname"/> </em> </td> </tr>
</logic:iterate> 标签handler
protected Iterator iterator = null;
protected Object collection = null;
protected String id = null;
protected String name = null;
protected String property = null;
protected String type = null;
public int doStartTag() throws JspException {
Object collection = this.collection;
if (collection == null) {
try {
Object bean = pageContext.findAttribute(name);
if (bean == null) {
... throw an exception
}
if (property == null)
collection = bean;
else
collection =
PropertyUtils.
getProperty(bean, property);
if (collection == null) {
... throw an exception
}
} catch
... catch exceptions thrown
by PropertyUtils.getProperty
}
}
// Construct an iterator for this collection
if (collection instanceof Collection)
iterator = ((Collection) collection).iterator();
else if (collection instanceof Iterator)
iterator = (Iterator) collection;
...
}
// Store the first value and evaluate,
// or skip the body if none
if (iterator.hasNext()) {
Object element = iterator.next();
pageContext.setAttribute(id, element);
return (EVAL_BODY_AGAIN);
} else
return (SKIP_BODY);
}
public int doAfterBody() throws JspException {
if (bodyContent != null) {
try {
JspWriter out = getPreviousOut();
out.print(bodyContent.getString());
bodyContent.clearBody();
} catch (IOException e) {
...
}
}
if (iterator.hasNext()) {
Object element = iterator.next();
pageContext.setAttribute(id, element);
return (EVAL_BODY_AGAIN);
} else
return (SKIP_BODY);
}
}
} 标签额外信息类
public VariableInfo[] getVariableInfo(TagData data) {
String type = data.getAttributeString("type");
if (type == null)
type = "java.lang.Object";
new VariableInfo(data.getAttributeString("id"),
type,
true,
VariableInfo.AT_BEGIN) };
}
} 模板标签库
JSP页面
<%@ page errorPage="errorpage.jsp" %>
<%@ include file="screendefinitions.jsp" %><html>
<head>
<title>
<tt:insert definition="bookstore"
parameter="title"/>
</title> </head>
<tt:insert definition="bookstore"
parameter="banner"/>
<tt:insert definition="bookstore"
parameter="body"/>
</body>
</html>
screen="<%= (String)request.
getAttribute(\"selectedScreen\") %>">
<tt:screen id="/enter">
<tt:parameter name="title"
value="Duke's Bookstore" direct="true"/>
<tt:parameter name="banner"
value="/banner.jsp" direct="false"/>
<tt:parameter name="body"
value="/bookstore.jsp" direct="false"/>
</tt:screen>
<tt:screen id="/catalog">
<tt:parameter name="title"
value="/<%=messages.getString("TitleBookCatalog")%>"
direct="true"/>
...
</tt:definition>
public void doGet(HttpServletRequest request,
HttpServletResponse response) {
request.setAttribute("selectedScreen",
request.getServletPath());
RequestDispatcher dispatcher =
request.getRequestDispatcher("/template.jsp");
if (dispatcher != null)
di
没有评论:
发表评论