jsp自定义标签开发

标签处理程序
在创建自定义标签之前,需要创建一个 标签处理程序。标签处理程序是一个执行自定义标签操作的 Java 对象。在使用自定义标签时,要导入一个 标签库 ―― 即一组标签/标签处理程序对。通过在 Web 部署描述符中声明库导入它,然后用指令 taglib 将它导入 JSP 页。 
如果 JSP 容器在转换时遇到了自定义标签,那么它就检查 标签库描述符(tag library descriptor) (TLD) 文件以查询相应的标签处理程序。TLD 文件对于自定义标签处理程序,就像 Web 部署描述符对于 servlet 一样。 
在运行时,JSP 页生成的 servlet 得到对应于这一页面所使用的标签的标签处理程序的一个实例。生成的 servlet 用传递给它的属性初始化标签处理程序。 
标签处理程序实现了 生存周期 方法。生成的 servlet 用这些方法通知标签处理程序应当启动、停止或者重复自定义标签操作。生成的 servlet 调用这些生存周期方法执行标签的功能。

标签的类型
可以定义两种类型的标签: 
•	javax.servlet.jsp.tagext.Tag 
•	javax.servlet.jsp.tagext.BodyTag 
对 正文 进行操作 ―― 即对在开始和结束标签之间的内容进行操作的 ―― 标签必须实现 BodyTag 接口。在这个教程中,我们将称这些标签为 正文标签。我们将不对其正文操作的标签称为 简单标签。简单标签可以实现 Tag 接口,尽管不要求它们这样做。要记住不对其正文操作的标签仍然 有 正文,只不过,它的标签处理程序不能读取这个正文。 
简单标签的例子
Struts 框架带有几个自定义标签库(有关 Struts 的更多信息的链接请参阅 参考资料 )。这些库中的一个标签可以创建一个支持改写 URL 的链接并用 jsessionid 对改写的连接编码。 
不过有一个问题:如果希望传递一组请求参数(如查询字符串),也许必须为此创建一个 Java scriptlet。真是乱!下面的清单 (search_results.jap) 展示了一个 JSP 页,它被迫加入了这样一个 scriptlet。 
 
1.	<%@ taglib uri="struts-html" prefix="html" %>  
2.	  
3.	  <jsp:useBean class="java.util.HashMap" id="deleteParams" />  
4.	  
5.	 <%    
6.	  
7.	    deleteParams.put("id", cd.getId());   
8.	  
9.	    deleteParams.put("method","delete");   
10.	  
11.	 %>  
12.	  
13.	 <!-- Pass the map named deleteParams to html:link to generate   
14.	 the request parameters-->  
15.	  
16.	 <html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  
17.	  
 
search_results.jsp 创建一个 hashmap 并向这个 map 传递两个属性。在下面几小节,我们将创建一个不用 Java 代码完成这项工作的自定义标签。我们的标签将定义如下的一个 hashmap: 
1.	<map:mapDefine id="deleteParams">             
2.	  
3.	    <map:mapEntry id="id" name="cd" property="id"/>  
4.	  
5.	    <map:mapEntry id="method" value="delete"/>  
6.	  
7.	</map:mapDefine>  
8.	  
9.	<!-- Pass the map named deleteParams to html:link to generate    
10.	the request parameters-->  
11.	  
12.	<html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  

 
这将使我们可以容易地创建小型 map。 
这个例子将展示几个关键概念,包括使用嵌套标签和定义 scriplet 变量。首先我将解释这个标签是如何工作的。然后在以后的几节中建立这些概念,并介绍如何编写这个标签的不同形式,使它们处理其正文并控制执行流程。 

构建简单标签的步骤
让我们创建一个定义一个 HashMap scriptlet 变量的标签。为此,需要实现标签处理程序接口 (javax.servlet.jsp.tagext.Tag)。因此,我们要创建的第一个标签将是一个简单标签。 
这个标签将实例化一个 map。使用这个标签的开发人员可以指定要实例化的 map 的类型 ―― HashMap、TreeMap、FastHashMap 或者 FastTreeMap。FastHashMap 和 FastTreeMap 来自 Jakarta Commons Collection library (有关链接请参阅 参考资料)。开发人员还可以指定标签所在的范围 ―― 页、请求、会话还是应用程序范围。 
要构建这个简单标签,我们需要完成以下步骤: 
1.	创建实现了 Tag 接口(准确地说是 javax.servlet.jsp.tagext.Tag)的标签处理程序类。
2.	创建一个 TLD 文件。
3.	在标签处理程序 Java 类中创建属性。 
4.	在 TLD 文件中定义与标签处理程序 Java 类中定义的属性对应的属性。 
5.	在 TLD 文件中声明 scriptlet 变量。
6.	实现 doStartTag() 方法。在标签处理程序类中,根据属性将值设置到 scriptlet 变量中。
如果您像我一样,可能会提前阅读书的结尾,所以请查看 附录 中标签处理程序类的完整列表以了解这个过程是如何结束的。 
在下面几小节中,我们将分析 MapDefineTag 的实现,并分析如何到达这一步。
第 1 步:创建一个实现了 Tag 接口的标签处理程序
为了编写标签处理程序,必须实现 Tag 接口。如前所述,这个接口用于不操纵其标签正文的简单标签处理程序。就像 J2EE API 文档 (有关链接请参阅 参考资料)所说的:Tag 接口定义了标签处理程序和 JSP 页实现类之间的基本协议。它定义了在标签开始和结束时调用的生存周期和方法。 
标签处理程序接口有以下方法: 
方法	作用
int doStartTag() throws JspException 	处理开始标签
int doEndTag() throws JspException 	处理结束标签
Tag getParent()/void setParent(Tag t) 	获得/设置标签的父标签
void setPageContext(PageContext pc) 	pageContext 属性的 setter 方法
void release() 	释放获得的所有资源
TagSupport 
现在,不必直接实现 Tag 接口,相反,用 map 定义的(map-defining)标签将继承 TagSupport 类。这个类以有意义的默认方法实现 Tag 接口,因而使开发自定义标签更容易 (有关 TagSupport 的 API 文档的链接请参阅 参考资料)。 例如,TagSupport 类定义了 get/setParent() 和 setPageContext(),这与所有标签处理程序几乎相同。 get/setParent() 方法允许标签嵌套。TagSupport 类还定义了一个可以被子类使用的 pageContext 实例变量 (protected PageContext pageContext),这个变量是由 setPageContext() 方法设置的。 
在默认情况下,TagSupport 实现了 doStartTag() 以使它返回 SKIP_BODY 常量,表示将不对标签正文进行判断。 此外,在默认情况下,doEndTag() 方法返回 EVAL_PAGE,它表示 JSP 运行时引擎应当对页面的其余部分进行判断。 最后,TagSupport 实现了 release(),它设置 pageContext 及其父元素为 null。 
TagSupport 类还实现了 IterationTag 接口和 doAfterBody(),这样它就返回 SKIP_BODY。 在后面讨论进行迭代的标签时我将对此加以更详细的解释(请参阅 用自定义标签控制流程)。 
好了,现在让我们通过继承 TagSupport 来实现 Tag 接口: 

  

 ...

 import javax.servlet.jsp.tagext.TagSupport;

 ...

 public class MapDefineTag extends TagSupport {

     ...

 
我们已经定义了标签处理程序,现在需要增加从处理程序到 TLD 文件中的标签的映射。我们将在下一小节中对此进行处理。然后,将完成 MapDefineTag 中剩余的代码。
 
  第 2 步:创建一个 TLD 文件
TLD 文件对自定义标签处理程序的作用就像 Web 部署描述符对 servlet 的作用。 TLD 文件列出了从标签名到标签处理程序的映射。 这个文件中的大多数数据都是在 JSP 页转换时使用的。 TLD 文件通常保存在 Web 应用程序的 WEB-INF 目录,并在 web.xml 文件中声明。它们一般用 .tld 扩展名结束。
TLD 文件有一个 导言(preamble),在这里标识 JSP 技术的版本和使用的标签库。这个导言通常看起来像这样: 

  

 <?xml version="1.0" encoding="UTF-8"?>

 <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
 "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

 

 <taglib>

 

    <tlib-version>1.0</tlib-version>

    <jsp-version>1.2</jsp-version>

    <short-name>map</short-name>

让我们更详细地分析一下这些标签:
•	TLD 文件的根元素是 taglib。taglib 描述了一个 标签库 ―― 即一组标签/标签处理程序对。 
•	因为我们使用的是 JSP 版本 1.2,所以在这个例子中需要 tlib-version 和 short-name 元素。
•	tlib-version 元素对应于标签库版本。 
•	jsp-version 对应于标签库所依赖的 JSP 技术的版本。
•	short-name 元素定义了 IDE 和其他开发工具可以使用的标签库的简单名。
•	taglib 元素包含许多 tag 元素,标签库中每一个标签有一个 tag 元素。
因为我们刚创建了自己的类,所以我们将继续往下进行,在 TLD 文件中声明这个类,如下所示: 

  

 <taglib>

    ...   

    <tag>

       <name>mapDefine</name>

       <tag-class>trivera.tags.map.MapDefineTag</tag-class>

       <body-content>JSP</body-content>

    ...

 
tag 元素用于将自定义标签映射到它们的自定义标签处理程序。上述清单中的 tag 元素将自定义标签 mapDefine 映射到处理程序 trivera.tags.map.MapDefineTag。 因此,不论在 mapDefine 上运行的是什么转换引擎,都会调用 trivera.tags.map.MapDefineTag。
已经在 TLD 中定义了标签,接下来要在标签处理程序类中定义这个标签的一些属性了。
 
第 3 步:在标签处理程序 Java 类中创建属性

我们希望为 mapDefine 标签指定三个属性,如下所示: 
属性说明
id 	新 scriptlet 变量的名字。
scope 	新 scriptlet 变量所在的范围。 
type 	新 scriptlet 变量的类型 (HashMap、FastHashMap、TreeMap 或者 FastTreeMap)。 如果 type 设置为 hash,那么就会创建一个 HashMap。如果 type 设置为 fasthash,那么将创建 FastHashMap。 
在 JSP 页中使用这个标签时,它看起来将像下面这样: 

  

 <map:mapDefine id="editParams" scope="session" type="hash">          
     ...
 </map:mapDefine>
这个标签将在会话范围内创建一个名为 editParams 的 HashMap。 
为了在标签处理程序中创建属性,需要定义相应的 JavaBean 属性。 因此,每一个属性在标签处理程序中都有对应的 setter 方法,如下所示: 

  

 public class MapDefineTag extends TagSupport {

     ...
     private String type = FASTTREE;

     private String id;
     private String scope;
     public void setType(String string) {
         type = string;
     }
     public void setId(String string) {
         id = string;
     }
     public void setScope(String string) {
         scope = string;
     }
转换引擎将用硬编码的配置数据或者运行时表达式设置这个标签的属性。 我们将在 第 4 步:在 TLD 文件中定义属性 中对此做更详细的讨论。
在 第 5 步:实现 doStartTag() 方法 中,我们将在标签处理程序的 doStartTag() 方法中使用这些属性。
第 4 步:在 TLD 文件中定义属性
就像上一小节中所做的那样,通过声明 JavaBean 属性定义自定义属性,然后在 TLD 文件中声明这些属性。 每一个 JavaBean 属性都必须与相应的自定义标签属性相匹配。 在 TLD 中定义的属性必须匹配 JavaBean 属性,不过却可以有与标签属性不匹配的 JavaBean 属性。 
下面是 MapDefineTag 的属性声明: 

  

    <tag>

 

       <name>mapDefine</name>

       <tag-class>trivera.tags.map.MapDefineTag</tag-class>

       <body-content>JSP</body-content>

 

       ...

       <attribute>

          <name>id</name>

          <required>true</required>

          <rtexprvalue>false</rtexprvalue>

          <description>The id attribute</description>

       </attribute>

       <attribute>

          <name>scope</name>

          <required>false</required>

          <rtexprvalue>false</rtexprvalue>

          <description>The scope attribute</description>

       </attribute>

       <attribute>

          <name>type</name>

          <required>false</required>

          <rtexprvalue>false</rtexprvalue>

          <description>

      Specifies the type of map valid values are fasttree, fasthash, hash, tree

      </description>

       </attribute>

    </tag>

    

 
name 元素指定属性的名字。required 元素指定属性是否是必需的(默认值是 false)。rtexprvalue 元素表明属性是硬编码了转换时的值还是允许使用运行时 scriptlet 表达式。 
记住,MapDefineTag 类必须为前面描述的每一个属性定义一个 JavaBean 属性,我们在 第 3 步:在标签处理程序 Java 类中创建属性 中完成这个任务。
第 5 步:实现 doStartTag() 方法
标签开始时调用 doStartTag() 方法 ―― 从开发人员的角度看,这是当引擎遇到 <map:mapDefine> 时发生的。如果 doStartTag() 返回 SKIP_BODY,那么将不处理标签正文。 如果它返回一个 EVAL_BODY_INCLUDE,那么将处理正文。 
MapDefine 类的 doStartTag() 方法完成以下工作: 
•	根据 type 属性确定要创建的 map 的属性。 
•	根据 scope 属性确定新的 map 对象放在什么范围内。 
•	根据 id 属性确定新 map 对象要放入的范围的名字。 
让我们更详细地分析这个过程。MapDefine 类检查 type 属性是设置为 FASTTREE、HASH、TREE 还是 FASTHASH。然后创建相应的 map,如下所示: 

  

     /* String constants for the different types of maps we support */

     public static final String FASTHASH = "FASTHASH";

     public static final String FASTTREE = "FASTTREE";

     public static final String HASH = "HASH";

     public static final String TREE = "TREE";

     

     /** The map we are going to create */

     private Map map = null;

     

     /** The member variable that holds the type attribute */

     private String type = FASTTREE;

     ...

 

     public int doStartTag() throws JspException {

 

         /** Based on the type attribute, determines which type of Map to create */

         if (type.equalsIgnoreCase(FASTTREE)) {

             map = new FastTreeMap();

         } else if (type.equalsIgnoreCase(HASH)) {

             map = new HashMap();

         } else if (type.equalsIgnoreCase(TREE)) {

             map = new TreeMap();

         } else if (type.equalsIgnoreCase(FASTHASH)) {

             map = new FastHashMap();

         }

         

 
然后,用 id 和 scope 属性将 hashmap 以一个给定的名字设置到一个给定范围中: 

  

 

     private String id;

     private String scope;

 

     public int doStartTag() throws JspException {

 

         ...        

         if (scope == null){

             pageContext.setAttribute(id, map);

         }else if("page".equalsIgnoreCase(scope)){

             pageContext.setAttribute(id, map);

         }else if("request".equalsIgnoreCase(scope)){

             pageContext.getRequest().setAttribute(id, map);

         }else if("session".equalsIgnoreCase(scope)){

             pageContext.getSession().setAttribute(id, map);

         }else if("application".equalsIgnoreCase(scope)){

             pageContext.getServletContext().setAttribute(id, map);

 

         }

         

         return EVAL_BODY_INCLUDE;

     }

 

 
如果范围属性是 null,那么 map 将放入页范围。否则,参数将放入通过 scope 属性传递的范围名所对应的范围中。 
到目前为止,我们已经有一个非常简单的标签,它有三个属性:id、scope 和 type。 我们将用给定的名字将 map 放到一个范围中。但是,我们还有一件事没做,就是声明 scriptlet 变量。 为了做到这一点,需要向 TLD 再添加一项,这就是我们在下一小节中所要做的事。
第 6 步:声明 scriptlet 变量

要理解 scriptlet 变量,必须理解 TLD 文件的作用。这个文件基本上是元数据的一个储存库,当 JSP 页转换为 servlet 时,标签会使用这些元数据。 在生成的 servlet 中,scriptlet 变量成了本地变量。 要让 JSP 转换引擎知道这些变量应当声明的类型,需要像下面这样在 TLD 文件中增加项: 

  

       <variable>

          <name-from-attribute>id</name-from-attribute>

          <variable-class>java.util.Map</variable-class>

          <scope>AT_BEGIN</scope>

       </variable>

 

 
将上面的代码片断放在 TLD 文件中 body-content 元素之后、attribute 元素之前。 在 variable 元素下面,我们声明了三个子元素:name-from-attribute、variable-class 和 scope。 name-from-attribute 指定 id 属性的值是转换引擎将要定义的 scriptlet 变量的名字。variable-class 是转换将要定义的变量的类类型。scope 指定变量什么时候可用:它可以嵌套到标签的正文中 (NESTED)、在标签结束后 (AT_END)、或者在标签的开始时 (AT_BEGIN)。我们使用 AT_BEGIN 这一范围,它意味着变量的范围将是从标签的开始到当前 JSP 页的结束。 
现在了解了如何构建简单自定义标签。在下一小节中,我们将分析标签的生存周期方法,以了解 JSP 页实际运行时所发生的情况。
简单标签的生存周期概述

如果像我第一次使用标签时那样,您可能发现不容易将转换引擎生存周期与运行时实际发生的事情对应起来。 像我一样,您可能看到很多试图说明这些概念的图表,这些图表看起来可能像下面这样。 
这里是已最小化的图像 
这个图表对我没什么意义,但是生成的代码有意义,所以我们将在这一小节中分析这些代码。(现在我觉得这些图表有意义了,但是我认为这些代码更能说明问题,在我们完成这一小节时,您可能想再次分析这个图表。) 
您可能还记得,JSP 页实际上是伪装的 servlet。JSP 文件在使用之前转换为 servlet。 下面是一个名为 testMapDefine.jsp 的小型 JSP 页,我们将用它来展示并测试在前面一节中开发的 MapDefineTag 标签: 

  

1.	<%@taglib uri="map" prefix="map"%>  
2.	  
3.	<html>  
4.	  
5.	<head><title>Test Map Define</title></head>  
6.	  
7.	<body>  
8.	  
9.	  
10.	  
11.	<map:mapDefine id="employee">  
12.	  
13.	    <br />  
14.	  
15.	    The employee is <%=employee%> <br />  
16.	  
17.	    <%   
18.	  
19.	    employee.put("firstName", "Kiley");   
20.	  
21.	    employee.put("lastName", "Hightower");   
22.	  
23.	    employee.put("age", new Integer(33));   
24.	  
25.	    employee.put("salary", new Float(22.22));   
26.	  
27.	    %>  
28.	  
29.	    The employee is <%=employee%> <br />  
30.	  
31.	</map:mapDefine>  
32.	  
33.	          
34.	  
35.	</body>  
36.	  
37.	</html>  

 
注意这一页用 map 的 URI 导入了一个自定义标签。之所以能这样是因为我们在 web.xml 文件中声明了这个 TLD,如下所示: 

  

 
1.	<web-app>  
2.	  
3.	         ...   
4.	  
5.	     <taglib>  
6.	  
7.	         <taglib-uri>map</taglib-uri>  
8.	  
9.	         <taglib-location>/WEB-INF/tlds/map.tld</taglib-location>  
10.	  
11.	     </taglib>  
12.	  
13.	 ...   

 
有另一种导入标签的方法。我认为这个方法更有用,因为它允许为 URI 指定一个短名,而短名容易记忆。 注意 taglib 是在其 TLD 文件上下文中描述的。在 WEB-INF 目录中的 map.tld 文件看起来像下面这样: 

  

 
1.	<?xml version="1.0" encoding="UTF-8"?>  
2.	  
3.	 <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"   
4.	 "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">  
5.	  
6.	    
7.	  
8.	 <taglib>  
9.	  
10.	    
11.	  
12.	    <tlib-version>1.0</tlib-version>  
13.	  
14.	    <jsp-version>1.2</jsp-version>  
15.	  
16.	    <short-name>map</short-name>  
17.	  
18.	    
19.	  
20.	    <tag>  
21.	  
22.	       <name>mapDefine</name>  
23.	  
24.	       <tag-class>trivera.tags.map.MapDefineTag</tag-class>  
25.	  
26.	       <body-content>JSP</body-content>  
27.	  
28.	    ...   

 
当 JSP 转换器遇到自定义标签 mapDefine 时,它将根据 taglib 指令和在 web.xml 文件中指定的 TLD 文件查询 TLD 文件。然后在生成的 servlet 中加入下面的代码: 

  

 
1.	public class _testmapdefine__jsp extends ...JavaPage{   
2.	  
3.	    
4.	  
5.	   public void _jspService(HttpServletRequest request,    
6.	    HttpServletResponse response) throws...{   
7.	  
8.	    
9.	  
10.	     trivera.tags.map.MapDefineTag tag0 = null;   
11.	  
12.	     java.util.Map employee = null;   
13.	  
14.	     try {   
15.	  
16.	       ...   
17.	  
18.	       if (tag0 == null) {   
19.	  
20.	         tag0 = new trivera.tags.map.MapDefineTag();   
21.	  
22.	         tag0.setPageContext(pageContext);   
23.	  
24.	         tag0.setParent((javax.servlet.jsp.tagext.Tag) null);   
25.	  
26.	         tag0.setId("employee");   
27.	  
28.	       }   
29.	  
30.	    
31.	  
32.	       int includeBody = tag0.doStartTag();   
33.	  
34.	       if (includeBody != javax.servlet.jsp.tagext.Tag.SKIP_BODY) {   
35.	  
36.	         employee = (java.util.Map)pageContext.findAttribute("employee");   
37.	  
38.	    
39.	  
40.	         out.print("<br /> \n The employee is "+ (employee) +"<br /> \n");           
41.	  
42.	    
43.	  
44.	     employee.put("firstName", "Kiley");   
45.	  
46.	     employee.put("lastName", "Hightower");   
47.	  
48.	     employee.put("age", new Integer(33));   
49.	  
50.	     employee.put("salary", new Float(22.22));   
51.	  
52.	        
53.	  
54.	         out.print("<br /> \n The employee is "+ (employee) +"<br /> \n");           
55.	  
56.	       }   
57.	  
58.	       employee = (java.util.Map)pageContext.findAttribute("employee");   
59.	  
60.	       ...   
61.	  
62.	     } catch (java.lang.Throwable _jsp_e) {   
63.	  
64.	       pageContext.handlePageException(_jsp_e);   
65.	  
66.	     ...   
67.	  
68.	   }   
69.	  
70.	 ...   
71.	  
72.	 }   

 

 
生成的 JSP servlet 声明了名为 tag0、类型为 trivera.tags.map.MapDefineTag 的一个本地变量。 然后它创建标签的一个实例、设置页上下文、设置父标签为 null,并设置 ID 为 employee。然后,生成的 servlet 调用 doStartTag() 方法,它检查返回类型是否设置为 Tag.SKIP_BODY。如果是,那么容器就不对标签的正文(在这里就是 if 块)进行判断。如果它返回 EVAL_BODY_INCLUDE,就像我们的标签那样,容器就将处理正文。 可以用这种技术有条件地加入标签的正文 ―― 即控制流程。 
注意生成的 servlet 有一个名为 employee 的本地变量, 根据标签的 id 属性将它设置为 employee。因此,转换引擎在 转换 时而不是运行时定义了一个名为 employee 的本地变量。这个概念使很多新人感到迷惑。 
理解嵌套标签 
前面的 JSP 页示例使用 JSP scriptlet 向 employee map 添加项。如果可以用另一个标签来完成就好了。 让我们定义一个名为 MapEntryTag 的嵌套标签,它通过调用 getParent() 并将它转换为 MapDefineTag 而得到其父标签。 
MapDefineTag 定义了 getMap() 方法,它返回新创建的 map。 doEndTag() 中的嵌套 MapEntryTag 使用 MapDefineTag 的 getMap() 方法向 map 中增加值,如下所示: 
 public class MapEntryTag extends TagSupport {
     String type = "java.lang.String";
     String id;
     String value;
     String name;
     String property;
     String scope;
 
     public int doEndTag() throws JspException {
         /* Grab the MapDefineTag using the getParent 
            method and cast it to a MapDefineTag.*/
         MapDefineTag mapDef = (MapDefineTag) this.getParent();
         Object objectValue = null;
         
                 ...
         /* Instantiate a new String, Integer or Float based on the type. */
         if (type.equals("java.lang.String")) {
             objectValue = value;
         } else if (type.equals("java.lang.Integer")) {
             Integer intValue = Integer.valueOf(value);
             objectValue = intValue;
         } else if (type.equals("java.lang.Float")) {
             Float floatValue = Float.valueOf(value);
             objectValue = floatValue;
         }
         
         /* Put the new entry into the map. */
         mapDef.getMap().put(id,objectValue);
         return EVAL_PAGE;

在上述代码的基础上,并假定在 TLD 文件中加入了必要的项,则可以像这样使用我们的标签: 
 <%@taglib uri="map" prefix="map"%>
 <html>
 <head><title>Test Map Define Entry</title></head>
 <body>
 
 <map:mapDefine id="employee">
    <map:mapEntry id="firstName" value="Jennifer"/>
    <map:mapEntry id="lastName" value="Wirth"/>
    <map:mapEntry id="age" value="33" type="java.lang.Integer"/>
    <map:mapEntry id="salary" value="22.22" type="java.lang.Float"/>   
 </map:mapDefine>
 
 The employee is set as <%=employee%> <br />
 
上述清单定义一个 employee map,其中带有针对 firstName、lastName、age 和 salary 各三项,它们的类型分别为 String、String、Integer 和 Float。 

用 Reflection 将 beann 属性提取为值

开发人员通常使用 Java Reflection 改进自定义标签代码。 在这一小节中,我们将用 Reflection 改写 MapEntryTag,使它可以使用所有范围内的所有 bean 属性定义 map 中的项。 例如,假设有像这样的一个 bean: 
 
 public class Test {
     
     String test="Jenny";
     public String getTest() {
         return test;
     }
 
     public void setTest(String string) {
         test = string;
     }
 
 }
 
改写后的标签可以将 bean 属性作为 map 中的项使用,像下面这样: 
  
 <jsp:useBean id="bean" class="trivera.tags.map.Test"/>
 
 <map:mapDefine id="employee2">
 
     <map:mapEntry id="firstName" name="bean" property="test"/>
     <map:mapEntry id="age" value="33" type="java.lang.Integer"/>
     <map:mapEntry id="salary" value="22.22" type="java.lang.Float"/>   
     <map:mapEntry id="properties" name="properties" />    
 </map:mapDefine>
 
注意 firstName 项现在是用 bean 的 test 属性定义的。
为了做到这一点,我们需要在自定义标签中加入 Relection,像这样(参看代码中的注释以了解改变的过程): 
 
 public class MapEntryTag extends TagSupport {...
 
 /* All of these have corresponding getters and setter method */
 String type = "java.lang.String"; //Holder for the type attribute
 String id;                        //Holder for the id attribute
 String value;                     //Holder for value attribute
 String name;                      //Holder for name attribute
 String property;                  //Holder for property attribute
 String scope;                     //Holder for scope attribute
 
 public int doEndTag() throws JspException {
     MapDefineTag mapDef = (MapDefineTag) this.getParent();
     Object objectValue = null;
     
     /* Check to see if the value property is set, 
     if it is then this is a simple entry */
     if (value !=null){
     
         if (type.equals("java.lang.String")) {
             objectValue = value;
         } else if (type.equals("java.lang.Integer")) {
             Integer intValue = Integer.valueOf(value);
             objectValue = intValue;
         } else if (type.equals("java.lang.Float")) {
             Float floatValue = Float.valueOf(value);
             objectValue = floatValue;
         }
     /* If it is not a simple entry, 
          then use reflection to get the property from the bean */
     }else {
         
         Object bean =null;
         
         if (scope == null){
             bean = pageContext.findAttribute(name);
         }else if("page".equalsIgnoreCase(scope)){
             bean = pageContext.getAttribute(name);
         }else if("request".equalsIgnoreCase(scope)){
             bean = pageContext.getRequest().getAttribute(name);
         }else if("session".equalsIgnoreCase(scope)){
             bean = pageContext.getSession().getAttribute(name);
         }else if("application".equalsIgnoreCase(scope)){
             bean = pageContext.getServletContext().getAttribute(name);
         }
         /* If the property attribute is null, 
                then just use the bean as the entry*/
         if (property==null){
             objectValue = bean;
             mapDef.getMap().put(id,bean);
         /* If the property attribute is set, 
                then use reflection to read the property */
         }else {
         
             try{
                 String propertyMethod = "get" +
                     property.substring(0,1).toUpperCase() + 
                     property.substring(1, property.length());
 
                 Method prop = bean.getClass()
                            .getMethod(propertyMethod,new Class[]{});
                 objectValue = prop.invoke(bean, new Object[]{});
             }catch(Exception e){
                 throw new RuntimeException(e);
             }
         }
             
     }
 
     mapDef.getMap().put(id,objectValue);
     return EVAL_PAGE;
 }
 看起来仅仅是为了实现一些很多标签都要有的功能就需要做大量的工作。幸运的是,有一个库使这种开发变得容易了。在下一小节分析这个库 ―― Struts ―― 是如何发挥作用的。 

用 Struts 简化自定义标签开发 
 
对 Struts 的深入讨论超出了本教程的范围(有关这个框架的更多信息请参阅 参考资料)。不过,如果熟悉这个框架,那么这些知识会对开发自定义标签有帮助。
也可以不使用 Reflection,而使用 Struts 的 RequestUtils 实现在上一小节看到的结果,如下所示: 

 public class MapEntryTag extends TagSupport {...
 private String type = "java.lang.String";
 ...
 public int doEndTag() throws JspException {
     MapDefineTag mapDef = (MapDefineTag) this.getParent();
     Object objectValue = null;
 if (value !=null){
     if (type.equals("java.lang.String")) {
         objectValue = value;
     } else if (type.equals("java.lang.Integer")) {
         Integer intValue = Integer.valueOf(value);
         objectValue = intValue;
     } else if (type.equals("java.lang.Float")) {
         Float floatValue = Float.valueOf(value);
         objectValue = floatValue;
     }
 }else {
     /** THIS USED TO BE 30 LINES OF CODE */
     objectValue = RequestUtils.lookup(pageContext, name, property, scope);
 }
 
     mapDef.getMap().put(id,objectValue);
     return EVAL_PAGE;
 }
 
可以看到,objectValue = RequestUtils.lookup(pageContext, name, property, scope); 这一行代替了使用 Reflection 的代码中的 30 行! Struts 自带了许多实用程序,使自定义标签的开发更容易了。 

你可能感兴趣的:(Web,jsp,bean,servlet,struts)