以JSP Tag文件形式开发自定义Action | |
|---|---|
| http://www.pcdog.com 2005-4-4 互联网 | |
| 在“JSP 2.0: 新特性”的最后一部分中,我们将要看两个新特性,它们使开发自定义标签库变得更加容易,它们是:标签文件和简化的tag-handler Java API。 以JSP Tag文件形式开发自定义Action JSP试图让对Java没有经验的人写动态页面成为可能。在页面间,重用一部分动态的、复杂的内容到目前为止仍然是令人痛苦的事情 JSP 2.0增加了第四种选择:也就是用标签文件的形式开发自定义action。一个标签文件是一个纯文本文件,你对所有的动态生成的部分使用JSP element,就像在常规的JSP页面中那样。和一个Java 标签handler有同样目的,即:为自定义的action 提供逻辑运算。标签文件和JSP 页面之间主要的区别是一个标签文件有一个以.tag为扩展名的标签文件,用这个扩展名来区别jsp页面,并且你可以使用仅在标签文件中有效的一些新的标签来定义输入和输出。 更进一步来看,这里有一个标签文件“poll.tag”,它生成一个投票的表单: <%@ tag body-content="empty" %> <%@ attribute name="question" required="true" %> <%@ attribute name="answers" required="true" type="java.lang.Object" %> <%@ attribute name="votesMapName" required="true" %> <%@ attribute name="answersMapName" required="true" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> Question: ${question}<br> <form action="result.jsp" target="result"> <input type="hidden" name="question" value="${question}"> <input type="hidden" name="votesMapName" value="${votesMapName}"> <input type="hidden" name="answersMapName" value="${answersMapName}"> <c:forEach items="${answers}" var="a"> <input type="radio" name="vote" value="${a.key}">${a.value}<br> </c:forEach> <input type="submit" value="Vote"> </form> 在文件的顶部,有一个标签指示符“tag”。这个标签指示符和你在JSP页面中使用的页面指示符“page”相似,它声明该文件的性质。这里,我使用了body-content属性来声明:在一个JSP页面中,一个表现这个标签文件的action element必须是empty,也就是这个action element不能有body。其他的值你可以使用“scriptless”(body可以包含除了脚本元素以外的任何期望的内容),或者“tagdependent”(容器传递主体内容给标签处理器(tag handler)而不做任何运算)。如果你曾经以Java class这种形式开发过自定义action,你可能就会从用来声明Java 标签处理器的TLD (Tag Library Descriptor)中认出“body-content”和“valid”。因为一个标签文件并不需要在一个TLD文件中被声明,标签指示符和其他特殊的标签文件指示符被用来提供相同类型的信息,TLD将这个信息提供给JSP容器。 在这个例子中跟随着这个标签指示符的是attribute指示符,用于同样的函数,和TLD 的element一样有着相同的名字:它声明有效的自定义action element 属性。这个自定义的用于投票的标签文件接受四个属性: question: 投票的问题 answers:一个具有application-scope作用范围的Map,用于保存投票的答案,用数字做主键(key) votesMapName:一个具有application-scope作用范围的变量名字,用于一个Map 保存每个答案的得票数,用答案数量做主键 answersMapName:一个具有application-scope作用范围的变量名字,用于保存答案的Map 每一个action element属性都有一个attribute指示符对应,它的名字用“name”属性来声明。在这个例子中,所有的action element属性都是需要的,就像对每一个attribute指示符都要用required属性来声明一样。“answers”属性值必须是一个“Map”,它被” type”属性来声明。所有其它的属性必须是String类型的,这是默认的类型,因此我没有特别为它们的属性指出类型。 一个taglib声明:在这个文件中使用了JSTL核心库,在一个用来输出question和每一个answer都有一个radio单选按钮的表单的EL表达式中,文件的主体使用了属性值(以page-scope变量形式,对标签文件有效)。answer 和 vote Map变量名字和question在表单中以隐藏域的形式在表单中出现,因此它们通过页面传递被处理,它们可以像下面这样应用: <%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Poll Results</title> </head> <body bgcolor="white"> <c:set target="${applicationScope[param.votesMapName]}" property="${param.vote}" value="${applicationScope[param.votesMapName][param.vote] + 1}" /> <p> Question: ${param.question}<br> <c:forEach items="${applicationScope[param.answersMapName]}" var="a"> ${a.key}) ${a.value}: ${applicationScope[param.votesMapName][a.key]}<br> </c:forEach> </p> </body> </html> 这是一个常规的JSP页面,使用了JSTL action。在保存投票结果的Map中,它对符合键值条件的被选中的投票答案加一。通过votesMapName参数提供名字,在application作用范围内有效。接下来,在页面上输出question并且通过Map迭代输出answer,通过answersMapName参数提供名字在application作用范围内有效, 向页面输出每一个answer和当前answer的得票数。 比如何处理poll vote更令人感兴趣的是如何在JSP页面中使用由poll tag标签文件实现的自定义action,这里有一个例子: <%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %> <html> <head> <title>My Page</title> </head> <body bgcolor="white"> <jsp:useBean id="myAnswers" scope="application" class="java.util.TreeMap"> <c:set target="${myAnswers}" property="1" value="Yes" /> <c:set target="${myAnswers}" property="2" value="No" /> <c:set target="${myAnswers}" property="3" value="Maybe" /> </jsp:useBean> <jsp:useBean id="myVotes" scope="application" class="java.util.HashMap" /> ... <p> <my:poll question="Will you start using tag files?" answers="${myAnswers}" answersMapName="myAnswers" votesMapName="myVotes" /> </p> ... </body> </html> 首先要注意的是“taglib”标签指示符,它声明使用的这个标签文件所在的标签库。要使用一个没有创建TLD的标签文件,你必须将这个标签文件保存在WEB-INF/tags目录下。例子中的poll.tag文件保存在WEB-INF/tags/mytags目录下,并且我让这个目录名字和“taglib”的“tagdir”属性的值相同。这样就告诉容器在这个目录下所找到的所有标签文件都属于同一个标签库,上面的例子中,在这个库中的action element被“taglib” 标签指示符的“prefix” 属性所指定的前缀所标识。另一种可选择的方式是:你可以将这些属于某个TLD的标签文件打包到一个jar文件中,用同样的“taglib” 标签指示符声明你使用了一个自定义标签库,并且用一个“uri”属性来替换“tagdir”属性。 在这个例子中,用于answer的“Map”对象和vote的计数由<jsp:useBean> action创建的,并且由JSTL的<c:set> action来操作,你当然可以用任何喜欢的方式来创建它们(比如:在一个context listener中,或者在Apache Struts application中用一个“plugin” class来创建)。无论它是如何被创建的,这个标签文件都是通过一个常规的JSP自定义action element被调用的。我使用一个EL表达式来对这个answers “Map”求值(“answers”的属性值)。标签文件默认是接受EL表达式的,你可以声明:一个静态值必须用“attribute”指示符的“rtexprvalue”属性来指定。 当你请求这个页面的时候,JSP容器就像通常一样对这个页面进行处理,通过element的“prefix” 和 “taglib“指示符的帮助来查找并定位 “<my:poll>”自定义标签的实现。容器可以按照它想要的任何方式来处理这个标签文件;Tomcat 5 容器把标签文件转换为一个Java标签处理器的类(tag handler class),编译并执行它。 处理自定义Action 就像用java语言编写的tag handler类一样,一个标签文件能要求容器处理自定义action element,更进一步地处理运算结果。 下面,在action element body中我们添加一个上一篇文章中poll question的小例子,就像下面这样: <%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="my" tagdir="/WEB-INF/tags/mytags" %> ... <html> ... <body bgcolor="white"> ... <p> <my:poll question="Will you start using tag files?" answers="${myAnswers}" answersMapName="myAnswers" votesMapName="myVotes" > JSP 2.0 introduces a new way to develop custom action tag handlers, called <i>tag files</i> </my:poll> </p> ... </body> </html> 这个例子仅仅包含文本,但是它也可以包含action element和EL 表达式。为了处理action element body并且讲结果添加到response中,我们需要象下面这样修改poll tag 文件: <%@ tag body-content="scriptless" %> <%@ attribute name="question" required="true" %> <%@ attribute name="answers" required="true" type="java.lang.Object" %> <%@ attribute name="votesMapName" required="true" %> <%@ attribute name="answersMapName" required="true" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <p> <jsp:doBody/> </p> Question: ${question}<br> <form action="result.jsp" target="result"> <input type="hidden" name="question" value="${question}"> <input type="hidden" name="votesMapName" value="${votesMapName}"> <input type="hidden" name="answersMapName" value="${answersMapName}"> <c:forEach items="${answers}" var="a"> <input type="radio" name="vote" value="${a.key}">${a.value}<br> </c:forEach> <input type="submit" value="Vote"> </form> 首先,我们改变“tag”指示符的“body-content”属性值为“scriptless”。就像我前面说过的那样,这意味着在这个文件的主体内容中可以包含除了scripting element以外的任何内容。接下来,我们添加一个“<jsp:doBody>” action,它告诉容器去处理body,并且将处理结果添加到response中。你可以有选择性的使用“var” 属性,捕获处理的结果,并在下一步进行处理。 另外对于我已经讲述的新特性来说,一个标签文件可以通过变量,未声明的属性将信息返回给调用文件,并且可以有 “fragment” 属性(也就是说这些属性可以拥有action element和EL表达式,标签文件处理EL表达式采用的方式与处理action element类似)。在我这本书第11章中,你可以看到关于这些特性的全部内容,你可以在http://www.oreilly.com/catalog/jserverpages3/chapter/下载程序范例。 简单的Java Tag-Handler API 采用标签文件的方式编写自定义action tag handler,这是一个重大的新特性。尤其对自定义action来说,它往往要生成很多的HTML。但是在处理某些事情时,仅仅用JSP action 和 EL 表达式是非常难以做到的,因此仍然还是需要Java tag-handler API 。首先,对于JSP 2.0来说,用Java编写一个tag handler是非常复杂的。这是由于在容器和tag handler之间的交互需要处理action element body,而为了在action element body中支持Java scripting element就变得复杂起来!毕竟,如果在body中仅仅是包含template text、EL表达式、action element的话,一种相对简单的API就可以被设计出来。而这就恰恰是JSP 2.0所做的,并且它被恰当地命名为simple tag handler API。 这里有一个前面的范例poll的自定义action的例子,在前面我们是用一个标签文件来实现的: package com.mycompany.mylib; import java.io.IOException; import java.util.Iterator; import java.util.Map; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; public class PollTag extends SimpleTagSupport { private String question; private Map answers; private String votesMapName; private String answersMapName; public void setQuestion(String question) { this.question = question; } public void setAnswers(Map answers) { this.answers = answers; } public void setVotesMapName(String votesMapName) { this.votesMapName = votesMapName; } public void setAnswersMapName(String answersMapName) { this.answersMapName = answersMapName; } public void doTag() throws JspException, IOException { JspWriter out = getJspContext().getOut(); JspFragment body = getJspBody(); if (body != null) { out.println("<p>"); body.invoke(null); out.println("</p>"); } out.print("Question:"); out.print(question); out.println("<br>"); out.println("<form action=\"result.jsp\" target=\"result\">"); out.print("<input type=\"hidden\" name=\"question\" value=\""); out.print(question); out.println("\">"); out.print("<input type=\"hidden\" name=\"votesMapName\" value=\""); out.print(votesMapName); out.println("\">"); out.print("<input type=\"hidden\" name=\"answersMapName\" value=\""); out.print(answersMapName); out.println("\">"); Iterator i = answers.keySet().iterator(); while (i.hasNext()) { String key = (String) i.next(); String value = (String) answers.get(key); out.print("<input type=\"radio\" name=\"vote\" value=\""); out.print(key); out.print("\">"); out.print(value); out.println("<br>"); } out.println("<input type=\"submit\" value=\"Vote\">"); out.println("</form>"); } } 一个singple tag必须实现新增的javax.servlet.jsp.tagext.SimpleTag接口。这个例子中的tag handler继承javax.servlet.jsp.tagext.SimpleTagSupport类,父类提供了所有方法默认的实现。就像经典的tag handler类一样,你需要给每一个自定义的action属性创建setter方法,但是这里仅仅有一个处理方法,那就是要实现doTag()方法。 在tag handler这个例子中,方法doTag()试图通过调用getJspBody()方法(从SimpleTagSupport类继承而来)而获得一个可以执行的action element body(一个JspFragment实例)的请求。如果找到这个body, tag handler就用一个null 值来调用它,就像对invoke()方法的争论一样,这意味着处理的结果被添加到response输出中。至于<jsp:doBody> action,通过一个Writer实例来代替invoke()方法,你能捕获到处理的结果。此时doTag()方法输出带有radio单选按钮的HTML表单,就像标签文件的实现一样。 因为容器只能调用一个方法来让tag handler处理自己该做的事情,这代替了经典的tag handler的三个方法(doStartTag(), doAfterBody(), and doEndTag(),每一个方法的返回值告诉容器下一步该做什么),实现一个SimpleTag的tag handler比经典的tag-handler API更容易。另外,simple tag-handler的实例永远不能被再次使用,因此你不需要担心重置后的状态,而它的状态将会导致很多的问题。在我前面的文章中描述过这个问题,具体见"JSP 1.2: Great News for the JSP Community, Part 2"。 在一个TLD中声明一个simple tag handler,所用的方式与你声明一个经典的tag handler一样。但是除了JSP,<body-content> TLD element必须有一个值。这是因为simple tag handler不允许scripting element出现在自定义action的element body中。除此之外,使用一个实现了simple tag handler的自定义action与使用一个实现了经典tag handler的action没有任何区别。 simple tag handler 和 classic tag handler都可以通过实现一个新的接口(javax.faces.jsp.tagext.DynamicAttributes)来支持未声明的attribute。两种类型的attribute 都可以是JspFragment,包含其他的action或者EL表达式,tag handler可以用它进行任意多次的处理。在我的《JavaServer Pages, 3rd Edition》这本书中,你可以看到更多关于这些特性的内容。 小节 在以上系列文章中,我已经向你展示了JSP 2.0中所有的新特性:EL表达式,更好的错误处理机制,新的配置选项,改进的XML页面格式,用来开发tag handler的两个新途径。总之,这些改进之处使开发JSP页面变得易于理解和维护。 如果你想要尝试JSP2.0的新特性,我建议你使用Apache Tomcat 5。它是最早实现了JSP新规范的容器之一。在公布最终版本的JSP 2.0规范之前,一个被标记为“stable”版本的Tomcat是不能被发布的。但是最新的beta版已经被证实是非常稳定的,不要理会beta版的标记。Tomcat 5在the Jakarta Project site可以下载。 | |
|
|