| 
   
带属性的标签 
在标签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> <br> <br> </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 href="<%=request.getContextPath()%>     /bookdetails?bookId=<%=bookId%>">     <strong><jsp:getProperty name="book"      property="title"/> </strong></a></td> 
    <td bgcolor="#ffffaa" rowspan=2>   <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> 
    <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 
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 {   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);     }   } }   
标签额外信息类 
有关脚本变量的信息是在IterateTei标签额外信息类中提供的。脚本变量的名字和类以标签属性的形式传入,并用于加入VariableInfo构造函数。 
public class IterateTei 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)     };   } }   
模板标签库 
模板提供了一种将应用程序中每一屏幕都会出现的共用元素与每一屏幕都会改变的元素分离开来的方法。将所有公共元素一起放到一个文件中更容易进行维护,并可以加强所有屏幕的外观一致性。它还使每一屏幕的开发更容易了,因为开发者只要注重于该屏幕特定的那部分内容就可以了,模板会负责公共部分。 
模板是JSP页面,在每一屏幕需要改变的地方有占位符。每一个占位符称为模板的参数。例如,一个简单的模板可能包含在生成的屏幕顶部的一个标题参数和JSP页面的正文参数以设定屏幕的定制内容。 
模板使用嵌入的标签——definition、screen和parameter——定义屏幕定义表并使用标签将屏幕定义插入到特定应用程序屏幕。 
JSP页面 
下面展示Duke's Bookstore例子的模板template.jsp。这一页面包括一个创建屏幕定义、并用insert标签将定义中的参数插入应用程序屏幕的JSP页面。 
<%@ taglib uri="/tutorial-template.tld" prefix="tt" %> <%@ 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>   
screendefinitions.jsp根据请求属性selectedScreen创建屏幕定义: 
<tt:definition name="bookstore"   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>   
模板由Dispatcher servlet实例化。Dispatcher首先得到所请求的屏幕并将它储存为请求的属性。这是必要的,因为在向template.jsp转发请求时,请求URL不包含原来的请求(如/bookstore3/catalog),而是反映转发布页面的路径(/bookstore3/template.jsp)。最后,servlet将请求分发给template.jsp: 
public class Dispatcher extends HttpServlet {   public void doGet(HttpServletRequest request,       HttpServletResponse response) {     request.setAttribute("selectedScreen",       request.getServletPath());      RequestDispatcher dispatcher =       request.getRequestDispatcher("/template.jsp");     if (dispatcher != null)       dispatcher.forward(request, response);   }   public void doPost(HttpServletRequest request,         HttpServletResponse response) {     request.setAttribute("selectedScreen",       request.getServletPath());     RequestDispatcher dispatcher =       request.getRequestDispatcher("/template.jsp");     if (dispatcher != null)       dispatcher.forward(request, response);   } }   
标签handler 
模板标签库包含四个标签handler——DefinitionTag、ScreenTag、ParameterTag和InsertTag,它们展示了协同操作标签的使用。DefinitionTag、ScreenTag和ParameterTag组成了一组嵌入的标签handler,它们共享公共(public)和私有(private)对象。DefinitionTag创建由InsertTag使用的名为definition的公共对象, 
在doStartTag中,DefinitionTag创建一个名为screens的公共对象,它包含屏幕定义的一个哈希表。屏幕定义包含屏幕标识符和一组与该屏幕相关联的参数。 
public int doStartTag() {   HashMap screens = null;   screens = (HashMap) pageContext.getAttribute("screens",     pageContext.APPLICATION_SCOPE);   if (screens == null)     pageContext.setAttribute("screens", new HashMap(),       pageContext.APPLICATION_SCOPE);    return EVAL_BODY_INCLUDE; }   
ScreenTag和ParameterTag用作为这些标签属性提供的文字填充屏幕定义表。表16-6显示了Duke's Bookstore应用程序的屏幕定义哈希表的内容。 
| 
 表16-6 屏幕定义  |  
| 
 屏幕Id   | 
 标题  | 
 横幅  | 
 正文  |  
| 
 /enter   | 
 Duke's Bookstore   | 
 /banner.jsp   | 
 /bookstore.jsp   |  
| 
 /catalog   | 
 Book Catalog   | 
 /banner.jsp   | 
 /catalog.jsp   |  
| 
 /bookdetails   | 
 Book Description   | 
 /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();   
 
   |