第13章 在JSP页面中自定义标签标准JSP标签是用来调用JavaBean组件的操作,处理定向请求以简化JSP页面开发与维护。JSP技术提供了一种封装其它动态类型的机制——自定义标签,它扩展了JSP语言。自定义标签通常发布在标签库中,该库定义了一个自定义标签集并包含实现标签的对象。 一些功能可以通过自定义标签来实现,包括对隐含对象的操作,处理表单,访问数据库集其它企业级服务,如e-mail,目录服务,处理流控制。JSP标签库由精通Java语言的开发者及精于访问数据机器它服务的专家来创建,由网络应用设计者来使用,以集中精力来解决表现而不是如何访问企业服务。业就是鼓励分开库的开发者与使用者相分离,自定义标签通过封装实现了生产率的提高。 标签库在JSP技术中受到广泛注意。要更多的了解标签库可以访问以下网址: http://java.sun.com/products/jsp/taglibraries.html 什么是自定义标签? 自定义标签是用户定义的JSP语言元素。当JSP页面包含一个自定义标签时被转化为servlet,标签转化为对称为tag handler的对象的操作。接着当servlet执行时Web container调用那些操作。 自定义标签有着丰富的特点,它们可以: 1、可以通过调用页面传递的属性进行自定义; 2、可以访问对于JSP页面可能的所有对象; 3、可以修改由调用页面产生的响应。 4、可以相互间通信。你可以创建并初始化一个JavaBean组件,创建一个变量引用标签中的bean,接着在其它的标签中引用该bean. 5、可以在一个标签中嵌套另一个,可以在JSP页面中进行复杂的交互。 JSP页面的例子 这章描述的任务包括使用及定义标签。 标签库Struts为建立一个国际化的网络应用提供了一个实现设计模式为Model-View-Control的框架。该标签库包括全面的功能用于处理: ☆ HTML forms ☆ Templates ☆ JavaBeans components ☆ Logic processing Duke’s Bookstore应用程序使用了这些标签:Struts bean和logic sublibraries. 标签库tutorial-template定义了一系列标签集来创建应用模板。该模板是带有占位符的JSP页面用来在每个页面显示需要改变的部分。每一个占位符作为模板的一个parameter而引用。例如,一个简单的模板可以包含一个标题参数及体参数来引用JSP页面中部分自定义的内容。模板由一系列嵌套的标签。 下图显示了通过Duke’sBookstroe的网络组件发出请求的流程:
Duke’s Bookstore3应用的源代码位于j2eetutorial/examples/src/web/bookstore3目录下。要编译、部署并运行该例子,进行一下操作步骤: 1、到目录j2eetutorial/examples下编译该应用程序。 2、从http://jakarta.apache.org/builds/jakarta-struts/release/v1.0下载并解压Struts(版本为1.0),将struts-bean.tld,struts-logic.tld和struts.jar拷到jakarta-struts-1.0/lib to examples/build/web/bookstore3下面 3、运行j2ee server; 4、运行部署工具deploytool 5、运行Cloudscape database; 6、如果你还没有创建bookstore database,运行ant create web-db. 7、创建J2EE应用程序——Bookstore3App. a. 选择File-àNew Application. b. 在文件选择框中,找到j2eetutorial/examples/src/web/bookstore3. c. 在文件名框中输入Bookstore3App; d. 单击New Application. e. OK. 8、创建WAR文件并将DispatcherServlet 网络组建及所有的Duke’s Bookstore加入到Bookstore3App. a. 选择File-àNew-àWeb Component. b. 单击创建New WAR文件,从复合框中选择Bookstore3App,输入Bookstore3WAR。 c. 单击Edit来增加文件。在编辑内容对话框中,导航到j2eetutorial/examples/build/web/bookstore3.选择Dispatcher.class并单击Add.增加banner.jsp,bookstore.jsp,bookdetails.jsp,catalog.jsp,showcart.jsp,cashier.jsp,receipt.jsp,initdestroy.jsp,template.jsp,screendefinitions.jsp,errorpage.jsp.增加duke.books.fig,struts-bean.tld,struts-logic.tld,tutorial-template.tld,struts.jar..增加cart,database,message,taglib,util packages. d. 单击Next e. 选择servlet单选按钮, f. 单击Next, g. 从Servlet类复选框中选择Dispatcher h. 单击两次Next. i. 在组件别名框中,单击增加接着输入别名。 j. 单击Finish. 9、增加BookDBEJB企业Bean a. 选择File-àAdd-àEJB JAR. b. 找到目录examples/build/web/ejb. c. 选择bookDB.jar, d. 单击Add EJBJAR. 10、给BookDBEJB增加一个引用。 a. 选择Bookstore3WAR, b. 选择EJB Refs标签, c. 单击Add, d. 在代码名称栏中输入ejb/BookDBEJB, e. 在类型栏中输入Session. f. 在接口列中选择Remote, g. 在Home接口列中输入database.BookDBEJBHome, h. 在Local/Remote接口栏中输入database.BookDBEJB. 11、增加标签库URI映射 a. 选择File Refs标签 b. 在JSP标签库德自框中单击Add按钮 c. 在引用栏中输入相对路径URI /tutorial-template, d. 在标签栏中输入绝对路径/WEB-INF/tutorial-template.tld, e. 对/struts-bean,struts-logic页执行上面的操作。 12、 指定JNDI名。 a. 选择Bookstore3App, b. 在应用表中,找到EJB组件并在JNDI名称栏中输入BookDBEJB, c. 在引用表中,定位到EJB Ref并在JNDI名称栏中输入BookDBEJB, d. 在引用表中,定位到资源组件并在JNDI名称栏中输入jdbc/Cloudscape. 13、输入context根 a. 选择Web Coontext 标签 b. 输入bookstore3. 14、部署应用程序 a. 选择Tools-àDeploy, b. 单击Finish. 15、打开bookstore URL http://<host>:8000/bookstore3/enter. 使用标签 这部分描述页面作者如何使用标签库指定JSP页面并介绍不同类型的标签。 声明标签 你可以通过在页面中使用下面的指令来声明在JSP中使用标签库: <%@ taglib uri=”WEB-INF/tutorial-template.tld” prefix=”tt” %> 该uri属性引用了唯一识别的标签库描述符(TLD),该URI可以是直接的也可以是间接的。Prefix属性定义了区分其它标签的方法。 标签库描述文件必须以后缀为.tld命名。TLD文件保存在WEB-INF目录中,你可以直接或间接的引用TLD。 下面的标签库指令直接引用了TLD文件: <%@ taglib uri=”/WEB-INF/tutorial-template.tld” prefix=”tt” %> 下面的标签库指令使用了短的逻辑名来间接引用TLD: <%@ taglib uri=”/tutorial-template” prefix=”tt” %> 逻辑名必须映射到Webapplication部署描述符中的绝对路径。为映射到逻辑名/tutorial-template到绝对路径/WEB-INF/tutorial-template.tld: 1、选择Bookstore3WAR, 2、选择File Refs标签, 3、在JSP标签库的子栏中单击Add按钮, 4、在代码引用域中输入相对路径URI /tutorial-template. 5、在标签库域中输入绝对路径/WEB-INF/tutorial-template.tld. 标签种类 JSP自定义标签使用XML语法。它们有起始标签及结束标签,并有可能有体: <tt:tag>body</tt:tag> 没有体的标签如下: <tt:tag /> 简单标签 一个简单标签没有体及属性:<tt:simple /> 带有属性的标签 自定义标签可以含有属性。属性列于起端标签,有这样的语法:attr=”value”.属性值用于自定义标签的行为,就像方法中定义的参数一样。 你可以给属性设置一字符串常量货运形时表达式。这个转换过程介于常量与运行时表达式之间,且属性类型遵循为JavaBean组件属性描述的规则。 Struts的属性 logic:present决定标签的体是否求值。在下面的例子中,一个属性制订了一个请求参数:Clear: <logic:present parameter=”Clear”> 在Duke’s Bookstore application页面catalog.jsp使用了运行表达式来设置属性值: <logic:iterate collection=”<%=bookDB.getBooks()%>” id=”book” type=”database.BookDetails”> 带体的标签 自定义标签可以包含自定义的核心标签,脚本元素,HTML文本,以及标签依赖的体内容。 在下面的例子中,Duke’s Bookstore application页面showcart.jsp使用了Struts logic:present 标签来清除购物撤兵打印消息如果请求中包含参数为Clear的话: <logic:present parameter=”Clear”> <% cart.clear(); %> <font color=”#ff0000” size=”+2”><strong> You just cleared your shopping cart!</strong><br> <br></font> </logic:present> 在传递信息时选择属性还是体 正如在后两节中讲到的,把一个给定的数据作为标签的属性或者体来传递是可能的。通常,无论是字符串或可以计算的简单表达式最好作为属性来传递。 定义脚本变量的标签 自定义标签可以定义在脚本中使用的变量。下面的例子展示了如何定义并使用包含从JNDIlookup返回的对象的脚本变量。这个例子包含企业bean,事务处理,数据库,环境入口等。 <tt:lookup id=”tx” type=”UserTransaction” name=”java:comp/UserTransaction” /> <% tx.begin(); %> 在Duke’sBookstore应用中,有些页面使用Struts的面向bean的标签。例如,bookdetails.jsp使用了bean:parameter标签来创建biikId变量并给它设置请求传递的参数。Jsp:setProperty语句也将bookDB的属性bookId设置为请求来的参数。标签bean:define返回属性bookDetails的值: <bean:parameter id=”bookId” name=”bookId” /> <jsp:setProperty name=”bookDB” property=”bookId” /> <bean:define id=”book” name=”bookDB” property=”bookDetails” type=”database.BookDetails” /> <h2><jsp:getProperty name=”book” property=”title”></h2> 标签的相互协作 顾客标签可以通过共享对象与其它标签相互协作。在下面的例子中,标签tag1创建了一个称为obj1的对象并重用tag2: <tt:tag1 attr1=”obj1” value=”value” /> <tt:tag2 attr1=”obj1” /> 在下一个例子中,一组嵌套的封闭标签创建了一个对象,所有内部标签都可以使用。由于对象未命名,这样减少了潜在的名字冲突。该例子展示了嵌套标签如何在jsp页面中协调工作的。 <tt:outerTag> <tt:innerTag /> </tt:outerTag> 定义标签 要定义一个标签,需要一下准备工作: ☆ 为标签开发一个标签处理累计帮助类 ☆ 在标签库中声明标签描述符 标签处理器 标签处理器是由网络容器调用的,用来处理运行的包含标签的JSP页面。标签处理器必须实现Tag或BodyTag接口。这些接口可以携带Java对象并产生一个标签处理器。对新创建的处理器,你可以使用TagSupport及BodyTagSupport类作为基类,这些类基接口在javax.servlet.jsp.tagext包中。 有Tag及BodyTag接口定义的处理器方法由JSP页面的servlet任何时候调用。当遇到标签的起始处时,JSP页面的servlet调用方法来初始化处理器接着调用处理器的doStartTag方法,当遇到结束点时,处理器的方法doEndTag被调用。另外的一些方法在标签相互通信时使用。 标签处理器有API接口来与jsp页面通信。其API入口点是page context(javax.servlet.jsp.PageContext)。通过API,处理器可以返回其它隐含对象(request,session,application). 隐含对象有些属性,它们可以通过使用[set|get]方法来调用。 如果标签嵌套,标签处理器也要访问相应的标签。 标签库描述符 标签库描述符是XML格式的文档。TLD包含库的所有信息及库中的每个标签。TLD由Web容器使用来验证标签并由JSP页面开发工具来使用。 TLD文件必须以扩展名.tld为后缀。这类文件保存在WEB-INFO目录中或它的子目录中,当你使用部署工具将TLD加到WAR中时,它会自动的加入到WEB-INFO中。 TLD文件必须以下面的格式开头: <?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> J2EE SDK1.3版可以识别1.1,1.2版的DTD。然而由于本章文档使用的是1.2版,所以必须使用更新的标签库。Struts库遵循1.1版的DTD,其中有些元素的名称有些改变。 TLD的根元素是taglib,子元素列表如下:
元素listener 标签库可以指定一些类——事件监听类。(参照处理servlet生命周期事件)。这些监听类都作为listener元素列于TLD中,网络容器将实例化监听类并通过与在WAR中定义的监听类类似的方法来注册。不像WAR级的监听类,,标签库中注册的监听类没有定义顺序。Listener元素的唯一子元素是listener-class,它必须包含监听类名的全称。 元素tag 库中的每个标签都有一个给定的名称及该标签的处理器来描述,标签创建的脚本变量信息,标签属性信息。脚本变量信息可以直接在TLD中给出或通过标签的额外信息获取。每一属性都包含说明是否需要该属性,它的值是否可以通过请求时表达式来决定,及其属性类型。 下表列出了标签的子元素信息:
下面的部分描述了需要为每种类型标签开发的方法几TLD元素。 简单标签 标签处理器 简单标签处理器必须实现doStartTag及doEndTag两个标签接口的方法。方法doStartTag在开始遇到时被调用,并返回SKIP_BODY,因为标签没有体。方法doEndTag在标签结束时被调用,如果其它页面需要使用则返回EVAL_PAGE,否则返回SKIP_PAGE. 在第一部分提到的标签:<tt:simple />将由下面的标签处理器来实现: public SimpleTag extends TagSupport { public int doStartTag() throws JspException { try{ pageContext.getOut().print(“hello.”); }catch(Exception e) {throw new JspTagException(“SimpleTag:”+e.getMessage());} return SKIP_BODY; } public int doEndTag() { return EVAL_PAGE; } } 元素body-content 没有体的标签必须用body-content元素来声明体内容为空: <body-content>empty</body-contnent> 标签属性 在标签处理器中定义属性 对于每一个标签属性,你必须在标签处理器中定义一个属性及get/set方法来遵循JavaBean的结构。例如,下面的处理器处理Struts logic:present tag. <logic :present parameter=”Clear”> 包含下面的声明及方法: protected String parameter=null; public String getParameter() { return (this.parameter); } public void setParameter(String parameter) { this.parameter=parameter; } 注意:如果属性命名为id,标签处理器继承了TagSupport类,你不需要定义该属性及set/get方法,因为已经由TagSupport定义了。 一个属性值类型为String的标签可以命名一隐含对象供标签处理器使用。隐含对象属性可以通过传递标签属性值到隐含对象的[get/set]Attribute方法来访问。这是一个好方法来传递脚本变量到标签处理器。 元素attribute 对于每一个标签属性,你必须指定该属性是否必须,该标签的知是否可以通过表达式确定,属性类型在元素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> 如果属性不必须,标签处理器应该指定一个默认值。 标签元素logic:present声明了一个属性parameter不必须,且它的值可以有一个表达式来赋值。 <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页面被运行时,网络容器将加强在TLD元素中的每一个属性的制约。 传递到标签的属性也可以在传递时间由一个继承于TagExtraInfo的类的方法isValid来验证。该类也用来提供标签定义的脚本变量的信息。 方法isValid传递对象TagData的属性信息,该对象包含了标签每一属性的属性值。由于验证过程在传输期间进行,所以属性值在请求时开始计算并送到TagData.REQUEST_TIME_VALUE. 标签<tt:twa attr1=”value1” />有下面的TLD属性元素: <attribute> <name>attr1</name> <required>true</required> <rtexprvalue>true</a> </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) { 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,这样的处理器实现了方法doInitBody,doAfterBody。这些方法与传递到标签处理器的体内容交互。 体内容支持一些方法来读写它的内容。标签处理器可以使用体内容的getString/getReader方法从体中获取信息,并通过writeOut方法来将体内容写到输出流。写提供writeOut方法,该方法可以通过使用标签处理器的方法getPreviousOut来获取。该方法确保标签处理器的结果对封闭的标签处理器可行。 如果标签的体需要计算,方法doStartTag需要返回EVAL_BODY_BUFFERED,否则,返回SKIP_BODY。 方法doInitBody 该方法在体内容设置之前求值之后被调用。通常使用该方法初始化体内容。 方法doAfterBody 该方法在体内容求值之后被调用。像方法doStartTag一样,doAfterBody方法必须返回指示是否继续计算体。这样,如果体需要再计算一次,就像实现了迭代标签一样,doAfterBody方法应返回EVAL_BODY_BUFFERED,否则返回SKIP_BODY. 方法release 标签处理器应该重新设置它的状态并通过方法release释放任何私有资源。 下面的例子从体中读取内容并传递到一个执行查询的对象。由于体不需要重复计算,所以doAfterBody返回SKIP_BODY。 Public class QueryTag extend BodyTagSupport { public int doAfterBody() throws JspTagException { BodyContent bc =getBodyContent(); String query=bc.getString(); Bc.clearBody(); Try { Statement stm=connection.createStatement(); Result=stm.executeQuery(query); }catch(SQLException e){ throw new JspTagException(“QueryTag:”+e.getMessage());} return SKIP_BODY; } } 元素body-content 对于有体的标签,必须用下面的方法指定体内容的类型: <body-content>JSP|tagdependent</body-content> 体内容包含自定义及核心标签,脚本元素,HTML文本都统一为JSP。这就是为Struts描述的logic:present标签。其它类型体内容,例如,SQL 语句传递查询标签,将会标以tagdependent. 注意,body-content元素的值不会影响体处理器的解释。 定义脚本变量的标签 标签处理器 标签处理器通过页面上下文设置的脚本变量负责创建、设置一个对象引用。它通过使用pageContext.setAttribute(name,value,scope)或pageContext.getAttribute(name,value)方法来实现。典型的,属性传递到自定义标签指定的脚本变量对象名;该对象可以通过调用属性的get方法返回。 如果脚本变量的值依赖于标签处理器上下文的一个对象。可以通过使用pageContext.getAttribute(name,scope)方法返回。 一般过程是标签处理器返回脚本变量,对对象进行处理,接着使用pageContext.setAttribute(name,object)方法给变量赋值。 对象受作用域总结如下表,作用于限制了对象的可访问性及对象的生命周期。
提供脚本变量的信息 在标签定义脚本变量的例子中,定义了一个脚本变量book用来访问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页面包含该标签并转换时,网络容器产生代码以与脚本变量保持同不。要产生代码,网络容器需要脚本变量的一些信息: ☆ 变量名 ☆ 变量类 ☆ 该变量是否引用一个新的或存在的对象 ☆ 该变量可访问 有两种方法可以提供这些信息:指定变量TLD子元素或定义标签的额外的信息类并在TLD中包含tei-class元素。使用变量元素很方便,但是不够灵活。 元素variable 元素variable有下面的一些子元素: name-given:变量名作为一个常量 name-from-attribute:属性名,它的解释时间将传给变量名。 上面两者之一是必需的,下面的子元素是可选的: variable-class:该变量的全名,缺省类型为java.lang.String. declare:该变量是否引用一个新对象。缺省值为True. scope:脚本变量的作用范围。 下表描述了变量的可用性及变量必须设置的方法
类TagExtraInfo 你可以通过扩展类javax.servlet.jsp.TagExtraInfo来定义一个额外标签信息类。TagExtraInfo必须实现方法getVariableInfo来返回一个VariableInfo对象数组,包含下面的一些信息: ☆ 变量名 ☆ 变量类 ☆ 该变量是否引用一个新对象 ☆ 该变量的可访问性 网络容器传递一个名为data的参数到方法getVariableInfo,这些属性用来提供对象VariableInfo。 Struts标签库提供了由bean:define创建的在标签额外信息类DefineTei中的脚本变量信息。由于脚本变量的名称及类作为标签的属性来传递,可以使用方法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 }; } } 为脚本变量定义的标签额外信息类必须在TLD中声明,这样元素tei-class将会如下: <tei-class>org.apache.struts.taglib.bean.DefineTagTei</tei-class> 与标签协作 通过共享对象实现标签的协作JSP技术支持两种类型的对象共享。第一种类型需要共享对象命名、存储在page context中。为了让其它标签访问,标签处理器使用pageContext.getAttribute(name,scope)方法。 第二种方法是由封闭的标签处理器创建的对象对内部的标签处理器是可访问的。这种形式的对象共享有个优点,它使用了对象私有的名字空间,这样减少了潜在的命名冲突。 为访问闭合标签创建的对象,标签处理器必须首先通过静态方法TagSupport.findAncestorWithClass(from,class)或TagSupport.getParent方法获得封闭标签。前一个方法应该在特定的嵌套的标签处理器不能保证时使用。一旦ancestor返回,标签处理器就能访问任何静态或动态创建的对象。静态创建的对象是父类的成员。私有对象也可以动态的创建。这样的对象可以通过方法setValue存储在标签处理器中,可以通过getValue方法返回。 下面的例子展示了一个标签处理器支持命名及私有对象访问共享对象的例子。在该例子中,,处理器为查询标签检验一个名称为connection的属性是否以设置到方法doStartTag中。如果connection属性已经设置,处理器将从page context对象返回connection对象,否则,标签处理器先返回封闭标签的处理器然后从处理器返回connection对象。 Public class QueryTag extends BodyTagSupport { Private String connectionId; Public int doStartTag() throws JspException { String cid=getConnection(); If(cid!=null){ Connection=(Connection)pageContext.getAttribute(cid); } else connectionTag ancestorTag=(ConnectionTag)findAncestorWithClass(this, ConnectionTag.class); If(ancestorTag==null) { Throw new JspTagException(“A quety without a connection attribute must be nested within a connection tag.”); Connection =ancestorTag.getConnection(); } } } 由标签处理器实现的查询标签可以在下面的方法中使用: <tt:connection id=”con01” …..>…..</tt:connection> <tt:query id=”balances” connection=”con01”> SLECT 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:conncetion> 在TLD中必须指出属性connection是可选的: <tag> … <attribute> <name>connection</name> <required>false</required> </attribute> </tag> 例子 自定义标签展示了在开发JSP应用中两种常见问题的解决方案:最小化在JSP页面中的编码量及确保良好的表现形式。为了达到这样的目的,在本章的前面几节展示了各类标签。 迭代标签 构造动态页面内容经常需要使用流控制语句。流控制标签可以减少这类语句的数量。 Struts的logic:iterate标签返回一个存储在JavaBean组建中的对象。并把它赋值给脚本变量。从脚本变量种返回的标签体信息 JSP页面 两个Duke’s应用页面,catalog.jsp和showcart.jsp.使用了logic:iterate标签来列出collection中的对象。下面展示了catalog.jsp的一段引用,这个JSP页面初始化了iterate标签,该标签设置了脚本变量book,代码片断如下: <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=”<%=requet.getContextPath()%>/bookdetails?bookId=<%=bookId%>”> 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%>”> </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> 标签处理器 标签logic:iterate的实现遵循JSP1.1规范,JSP1.2规范增加了一些新特性。下面将进行讨论: 标签logic:iterate支持几种方法来初始化collection:作为标签属性或者是作为bean或bean的属性。 标签额外信息类 在IterateTei标签的额外信息类中提供了脚本变量的信息。脚本变量的名称及类传递到构造器VariableInfo。 Public class InterateTei extends TagExtraInfo { Public VariableInfo[] getVariableInfo(TagaData 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页面 在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 difinition=”bookstore” parameter=”body”/> </body> </html> 页面screendefinitions.jsp创建了一个基于请求属性的页面定义 <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.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不包含原始请求板反映了定向路径,最后servlet将请求重定向到template.jsp public class Dispatcher extends HttpServlet{ public void doGet(HttpServletRequest request,HttpServletResponse response){ request.setAttribute(“selsectedScreen”,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(rquest,response); } } 标签处理器 模板标签库包含四个标签处理器——DefinitionTag,ScreenTag,ParameterTag,InsertTag,DefinitionTag,ScreenTag,ParameterTag组成了一系列嵌套的标签处理器,他们可以共享共有的及私有的对象。DefinitionTag创建了一个共有的对象definition供InsertTag使用。 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; } 下面的表格是关于Duke’s Bookstore应用的哈西表
在doEndTag中,DefinitionTag创建了一个公有的对象Definition: public int doEndTag() throws JspTagException { try { Definition definition=new Definition(); Hashtable 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 difinition in the page context. PageContext.setAttribute( DefinitionName,definition); }catch(Exception ex) { ex.printStackTrace(); } return EVAL_PAGE; } 如果传递给请求的URL是/enter,Definition包含下面的一些项:
URL/enter的定义如下表
InsertTag使用Definition插入一个参数到响应中。在方法doStartTag中,它从page context返回对象definition. 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 doTndTag() throws JspTagException { Try{ If(directInclude&¶meter!=null) PageContext.getOut().print(parameter.getValue()); Else{
If((parameter!=null)&&(parameter..getValue()!=null)) PageContext..include(parameter.getValue()); } }catch(Exception e){ throw new JspTagException(e.getMessage()); } return EVAL_PAGE; } 标签处理器是如何被调用的? 标签接口定义了位于标签处理器和JSP页面servlet之间的基础协议。它定义了在遇到标签起始及结束处是要调用的方法。 JSP servlet调用setPageContext,setParent及在调用doStartTag方法之前调用属性设置方法。JSP servlet也负责在结束页面之前调用release以释放资源。 下面是一个典型的标签处理器方法调用顺序的例子: ATag t =new Atag(); t.setPageContext(…); t.setParent(…); t.setAttribute(value1); t.setAttribute2(value2) t.doStartTag(); t.doEndTag(); t.release(); 接口BodyTag扩展自Tag,它允许标签处理器访问body。该接口提供了三个新方法: .setBodyContent .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(); |