自定義標籤&&JSTL標籤庫詳解

今天來看一下自定義標籤的內容,自定義標籤是JavaWeb的一部分非常重要的核心功能,我們之前就說過,JSP規範說的很清楚,就是Jsp頁面中禁止編寫一行Java代碼,就是最好不要有Java腳本片段,下面就來看一下自定義標籤的簡介:

自定義標籤主要用於移除Jsp頁面中的java代碼。
移除jsp頁面中的java代碼,只需要完成兩個步驟:
編寫一個實現Tag接口的Java類,並覆蓋doStartTag方法,把jsp頁面中的java代碼寫到doStartTag方法中。
編寫標籤庫描述符(tld)文件,在tld文件中對自定義標籤進行描述。
完成以上操作,即可在JSP頁面中導入和使用自定義標籤。
快速入門:使用自定義標籤輸出客戶機IP
查看tag接口api文檔,分析自定義標籤的執行流程。


下面來看一下一個簡單的Demo使用自定義標籤打印客戶機的IP地址

首先我們自定義標籤類:ViewIpTag

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.traditionaltag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.http.HttpServletRequest;  
  6. import javax.servlet.jsp.JspException;  
  7. import javax.servlet.jsp.JspWriter;  
  8. import javax.servlet.jsp.tagext.TagSupport;  
  9.   
  10. /** 
  11.  * 自定義標籤,然後將這個標籤映射到這個類:mytag:viewIP 
  12.  * 記得將自定義的標籤綁定到一個url上面,這個url一般是公司的網址 
  13.  *  
  14.  */  
  15. public class ViewIpTag extends TagSupport{  
  16.   
  17.     private static final long serialVersionUID = 1L;  
  18.   
  19.     @Override  
  20.     public int doStartTag() throws JspException {  
  21.         //內置一個pageContext對象,我們之前說到pageContext對象,它裏面是封裝了9個隱式對象  
  22.         HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest();  
  23.         JspWriter out = this.pageContext.getOut();  
  24.         String ip = request.getRemoteAddr();  
  25.         try {  
  26.             out.print(ip);  
  27.         } catch (IOException e) {  
  28.             throw new RuntimeException(e);  
  29.         }  
  30.         return super.doStartTag();  
  31.     }  
  32.   
  33. }  

自定義tld文件,mytag.tld

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2.   
  3. <taglib xmlns="http://java.sun.com/xml/ns/j2ee"  
  4.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"  
  6.     version="2.0">  
  7.       
  8.   <description>JSTL 1.1 core library</description>  
  9.   <display-name>JSTL core</display-name>  
  10.   <tlib-version>1.1</tlib-version>  
  11.   <short-name>weijia</short-name>  
  12.   <uri>http://www.weijia.cn/mytag</uri>  
  13.   
  14.   <!-- 顯示IP地址 -->  
  15.   <tag>  
  16.     <description>  
  17.         Catches any Throwable that occurs in its body and optionally  
  18.         exposes it.  
  19.     </description>  
  20.     <name>viewIP</name>  
  21.     <tag-class>com.weijia.traditionaltag.ViewIpTag</tag-class>  
  22.     <body-content>empty</body-content>  
  23.   </tag>  
  24. </taglib>  
這裏我們將就自定義的標籤類就註冊好了,下面解釋一下這些字段的含義:

首先看一下:

<short-name>這個標籤是指定我們定義標籤的簡稱,這個作用不大

<uri>這個標籤是給這個標籤文件指定一個訪問路徑,這個路徑我們在Jsp頁面中引入這個標籤的時候需要用到

<tag-class>這個標籤就是指定我們自定義的標籤類的全稱

<body-content>這個標籤表明自定義標籤是否有標籤體內容(empty:沒有,JSP:有)


我們註冊之後標籤類了,下面就在Jsp頁面中進行使用了,這時候就要用到我們之前說到的Jsp的指令中的taglib了,格式如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <%@ taglib uri="http://www.weijia.cn/mytag" prefix="mytag" %>  
這個就將我們定義的標籤引入到Jsp頁面中了,其中我們uri屬性的值就是我們在標籤定義文件mytag.tld中指定的那個uri那個標籤值,當然這裏的uri也可以直接指定mytag.tld文件的路徑即:/WEB-INF/mytag.tld 也是可以的,其實我們查看翻譯之後的Jsp代碼可以看到,不管用那種方式,他其實加載的時候都是去找真是路徑中文件:



其中prefix屬性的值是標籤前綴名,這個名稱就是我們在Jsp頁面中使用的標籤前綴,這個值一般和tld文件的文件名是保持一致的


下面就是在Jsp中使用標籤:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. 客戶機的IP地址是:<mytag:viewIP/>  
這樣就是打印了客戶機的IP地址,這裏我們在Jsp頁面中就沒有Java代碼了


上面我們介紹了一個簡單的例子,下面我們來詳細看一下這個自定義標籤的執行原理:

JSP引擎將遇到自定義標籤時,首先創建標籤處理器類的實例對象,然後按照JSP規範定義的通信規則依次調用它的方法。
1、public void setPageContext(PageContext pc), JSP引擎實例化標籤處理器後,將調用setPageContext方法將JSP頁面的pageContext對象傳遞給標籤處理器,標籤處理器以後可以通過這個pageContext對象與JSP頁面進行通信。
2、public void setParent(Tag t),setPageContext方法執行完後,WEB容器接着調用的setParent方法將當前標籤的父標籤傳遞給當前標籤處理器,如果當前標籤沒有父標籤,則傳遞給setParent方法的參數值爲null。
3、public int doStartTag(),調用了setPageContext方法和setParent方法之後,WEB容器執行到自定義標籤的開始標記時,就會調用標籤處理器的doStartTag方法。
4、public int doEndTag(),WEB容器執行完自定義標籤的標籤體後,就會接着去執行自定義標籤的結束標記,此時,WEB容器會去調用標籤處理器的doEndTag方法。
5、public void release(),通常WEB容器執行完自定義標籤後,標籤處理器會駐留在內存中,爲其它請求服務器,直至停止web應用時,web容器纔會調用release方法。

我可以查看我們上面的例子翻譯後的Jsp代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. out.write("<body>  \r\n");  
  2. out.write("\t<!-- 顯示客戶機的IP地址 -->\r\n");  
  3. out.write("\t客戶機的IP地址是:");  
  4. if (_jspx_meth_mytag_005fviewIP_005f0(_jspx_page_context))  
  5.     return;  
  6. out.write("\r\n");  
  7. out.write("\t\r\n");  


再來看一下那個if中的方法的代碼:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private boolean _jspx_meth_mytag_005fviewIP_005f0(PageContext _jspx_page_context)  
  2.           throws Throwable {  
  3.     PageContext pageContext = _jspx_page_context;  
  4.     JspWriter out = _jspx_page_context.getOut();  
  5.     //  mytag:viewIP  
  6.     com.weijia.traditionaltag.ViewIpTag _jspx_th_mytag_005fviewIP_005f0 = (com.weijia.traditionaltag.ViewIpTag) _005fjspx_005ftagPoo    l_005fmytag_005fviewIP_005fnobody.get(com.weijia.traditionaltag.ViewIpTag.class);  
  7.     _jspx_th_mytag_005fviewIP_005f0.setPageContext(_jspx_page_context);  
  8.     _jspx_th_mytag_005fviewIP_005f0.setParent(null);  
  9.     int _jspx_eval_mytag_005fviewIP_005f0 = _jspx_th_mytag_005fviewIP_005f0.doStartTag();  
  10.     if (_jspx_th_mytag_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {  
  11.       _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);  
  12.       return true;  
  13.     }  
  14.     _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);  
  15.     return false;  
  16.   }  
我們可以看到,首先這個方法接收的是一個pageContext變量對象,這個和我們之前說的一樣,自定義標籤類中有一個pageContext變量對象就可以操作其他對象了,下面來看一下那個方法的代碼,首先他會去加載那個標籤類,同時注意到首先是執行setPageContext()方法的,將pageContext變量傳遞到標籤類中,然後看setParent()方法傳遞的是null,因爲我們打印IP的標籤沒有父標籤的,接下來執行doStartTag()方法,然後再執行doEndTag()方法,這裏我們看到是做個判斷,如果doEndTag方法返回的值是Tag.SKIP_PAGE的話,就是說餘下的jsp頁面不執行了,所以返回一個true,那麼我們看到上面的if判斷代碼中,如果這個方法返回true的話,直接return,下面的代碼就不執行了。大體流程就是這樣的。


下面我們用自定義標籤來實現一些特定需求的功能:

1、不執行標籤體內容

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.traditionaltag;  
  2.   
  3. import javax.servlet.jsp.JspException;  
  4. import javax.servlet.jsp.tagext.TagSupport;  
  5.   
  6. /** 
  7.  * 是否輸出標籤體內容 
  8.  * @author weijiang204321 
  9.  * 
  10.  */  
  11. public class TagDemo1 extends TagSupport{  
  12.   
  13.     @Override  
  14.     public int doStartTag() throws JspException {  
  15.         return TagSupport.EVAL_BODY_INCLUDE;//輸出標籤體內容  
  16.         //return TagSupport.SKIP_BODY;//不輸出標籤體內容  
  17.     }  
  18.   
  19. }  

我們看到只要doStartTag方法返回TagSupport.EVAL_BODY_INCLUDE常量,就會執行標籤體內容,如果返回的是TagSupport.SKIP_BODY常量,就不會執行標籤體內容,代碼很簡單。


下面我們再來註冊這個標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 是否顯示標籤體 -->  
  2.  <tag>  
  3.    <description>  
  4.        Catches any Throwable that occurs in its body and optionally  
  5.        exposes it.  
  6.    </description>  
  7.    <name>demo1</name>  
  8.    <tag-class>com.weijia.traditionaltag.TagDemo1</tag-class>  
  9.    <body-content>JSP</body-content>  
  10.  </tag>  

因爲是有標籤體內容的,所以<body-content>標籤的值是JSP


在Jsp頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 不執行標籤體 -->  
  2. <simpletag:demo1>  
  3.     aaaa  
  4. </simpletag:demo1>  


2、控制JSP餘下頁面的內容不執行

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.traditionaltag;  
  2.   
  3. import javax.servlet.jsp.JspException;  
  4. import javax.servlet.jsp.tagext.TagSupport;  
  5.   
  6. /** 
  7.  * 控制整個JSP是否輸出 
  8.  * @author weijiang204321 
  9.  * 
  10.  */  
  11. public class TagDemo2 extends TagSupport{  
  12.   
  13.     @Override  
  14.     public int doStartTag() throws JspException {  
  15.         return super.doStartTag();  
  16.     }  
  17.   
  18.     @Override  
  19.     public int doEndTag() throws JspException {  
  20.         return TagSupport.EVAL_PAGE;  
  21.         //return TagSupport.SKIP_PAGE;不執行餘下的jsp內容  
  22.     }  
  23.   
  24. }  
當doEndTag方法返回的是TagSupport.EVAL_PAGE常量的話就執行jsp餘下的內容,如果返回的是TagSupport.SKIP_PAGE常量的話就不執行jsp餘下的內容


在tld文件中註冊這個自定義標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 控制是否顯示jsp頁面 -->  
  2. <tag>  
  3.   <description>  
  4.       Catches any Throwable that occurs in its body and optionally  
  5.       exposes it.  
  6.   </description>  
  7.   <name>demo2</name>  
  8.   <tag-class>com.weijia.traditionaltag.TagDemo2</tag-class>  
  9.   <body-content>empty</body-content>  
  10. </tag>  

在JSP頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 不執行餘下的頁面內容 -->  
  2. <simpletag:demo2/>  
這樣使用之後,在這個標籤之後的內容就不會執行了,我們在上面分析源代碼的時候已經解析過了。頁面都不會含有餘下的內容了,如果我們將這個標籤放在頁面的第一行,那麼這個頁面就是一片空白,我們在瀏覽器中查看頁面的源代碼,也是發現一片空白的,因爲out對象沒有進行print了


3、重複執行標籤體內容

自定義標籤體類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.traditionaltag;  
  2.   
  3. import javax.servlet.jsp.JspException;  
  4. import javax.servlet.jsp.tagext.TagSupport;  
  5.   
  6. /** 
  7.  * 控制標籤體重複執行 
  8.  * @author weijiang204321 
  9.  * 
  10.  */  
  11. public class TagDemo3 extends TagSupport{  
  12.   
  13.     private int count = 5;  
  14.       
  15.     @Override  
  16.     public int doStartTag() throws JspException {  
  17.         return TagSupport.EVAL_BODY_INCLUDE;  
  18.     }  
  19.       
  20.     @Override  
  21.     public int doAfterBody() throws JspException {  
  22.         count--;  
  23.         if(count > 0){  
  24.             return TagSupport.EVAL_BODY_AGAIN;//執行完之後接着執行doAfterBody()方法  
  25.         }else{  
  26.             return TagSupport.SKIP_BODY;  
  27.         }  
  28.     }  
  29.   
  30.     @Override  
  31.     public int doEndTag() throws JspException {  
  32.         return TagSupport.SKIP_BODY;  
  33.     }  
  34.   
  35. }  

這裏我們需要在doAfterBody方法中操作了,因爲這個方法的返回值爲TagSupport.EVAL_BODY_AGAIN常量的話,這個方法還會被調用,直到這個方法返回TagSupport.SKIP_BODY,所以我們這裏控制標籤體內容執行5次,我們定義一個變量就可以了,然後控制doAfterBody方法的返回值,這裏還要注意的是,在doStartTag方法中返回值是TagSupport.EVAL_BODY_INCLUDE常量,因爲我們要執行標籤體內容的。


註冊自定義標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 控制標籤體重複輸出 -->  
  2. <tag>  
  3.   <description>  
  4.       Catches any Throwable that occurs in its body and optionally  
  5.       exposes it.  
  6.   </description>  
  7.   <name>demo3</name>  
  8.   <tag-class>com.weijia.traditionaltag.TagDemo3</tag-class>  
  9.   <body-content>JSP</body-content>  
  10. </tag>  

在Jsp頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 重複執行標籤體內容 -->  
  2. <simpletag:demo3>  
  3.     aaaa  
  4. </simpletag:demo3>  
這時候在頁面中就會輸出5個aaaa


4、修改標籤體內容

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.traditionaltag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.BodyContent;  
  7. import javax.servlet.jsp.tagext.BodyTagSupport;  
  8.   
  9. /** 
  10.  * 修改標籤體內容 
  11.  * @author weijiang204321 
  12.  * 
  13.  */  
  14. public class TagDemo4 extends BodyTagSupport{  
  15.   
  16.     @Override  
  17.     public int doEndTag() throws JspException {  
  18.         BodyContent bc = this.getBodyContent();//獲取標籤體內容對象  
  19.         String content = bc.getString();  
  20.         content = content.toUpperCase();//將標籤體內容轉成大寫  
  21.         try {  
  22.             this.pageContext.getOut().write(content);//在將轉化之後的內容輸出到瀏覽器中  
  23.         } catch (IOException e) {  
  24.             throw new RuntimeException(e);  
  25.         }  
  26.         return BodyTagSupport.EVAL_BODY_INCLUDE;  
  27.     }  
  28.   
  29.     @Override  
  30.     public int doStartTag() throws JspException {  
  31.         return BodyTagSupport.EVAL_BODY_BUFFERED;//這裏返回緩存標籤體內容常量  
  32.     }  
  33.   
  34. }  

這裏我們要注意的是,我們繼承的是BodyTagSupport類了,要在doStartTag方法中返回BodyTagSupport.EVAL_BODY_BUFFERED常量,纔可以取出標籤體內容緩存,然後再doEndTag方法中取出標籤體內容然後進行操作之後再寫到瀏覽器中。


註冊我們的自定義標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 修改標籤體內容 -->  
  2.   <tag>  
  3.     <description>  
  4.         Catches any Throwable that occurs in its body and optionally  
  5.         exposes it.  
  6.     </description>  
  7.     <name>demo4</name>  
  8.     <tag-class>com.weijia.traditionaltag.TagDemo4</tag-class>  
  9.     <body-content>JSP</body-content>  
  10.   </tag>  

在Jsp頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 修改標籤體內容 -->  
  2. <simpletag:demo4>  
  3.     bbbb  
  4. </simpletag:demo4>  
這時候在瀏覽器中輸出的是:BBBB


上面說到的是Jsp2.0以前的自定義標籤的方法,從Jsp2.0以後,我們就開始使用了簡單標籤類SimpleTagSupport,因爲我們可以看到Jsp2.0之前的是傳統標籤類的話,要想實現不同的功能,還需要繼承不同的類,比如:TagSupport,BodyTagSupport,這樣會增加開發成本,所以Jsp2.0之後引入了簡單標籤類SimpleTagSupport了,那麼下面我們先來看一下簡單標籤的執行流程:

由於傳統標籤使用三個標籤接口來完成不同的功能,顯得過於繁瑣,不利於標籤技術的推廣, SUN公司爲降低標籤技術的學習難度,在JSP 2.0中定義了一個更爲簡單、便於編寫和調用的SimpleTag接口來實現標籤的功能。實現SimpleTag接口的標籤通常稱爲簡單標籤。簡單標籤共定義了5個方法:
setJspContext方法
setParent和getParent方法
setJspBody方法
doTag方法

下面來看一下這些方法的解釋
setJspContext方法
用於把JSP頁面的pageContext對象傳遞給標籤處理器對象 
setParent方法
用於把父標籤處理器對象傳遞給當前標籤處理器對象 
getParent方法
用於獲得當前標籤的父標籤處理器對象 
setJspBody方法
用於把代表標籤體的JspFragment對象傳遞給標籤處理器對象 
doTag方法
用於完成所有的標籤邏輯,包括輸出、迭代、修改標籤體內容等。在doTag方法中可以拋出javax.servlet.jsp.SkipPageException異常,用於通知WEB容器不再執行JSP頁面中位於結束標記後面的內容,這等效於在傳統標籤的doEndTag方法中返回Tag.SKIP_PAGE常量的情況。 

當web容器開始執行標籤時,會調用如下方法完成標籤的初始化
WEB容器調用標籤處理器對象的setJspContext方法,將代表JSP頁面的pageContext對象傳遞給標籤處理器對象。
WEB容器調用標籤處理器對象的setParent方法,將父標籤處理器對象傳遞給這個標籤處理器對象。注意,只有在標籤存在父標籤的情況下,WEB容器纔會調用這個方法。
如果調用標籤時設置了屬性,容器將調用每個屬性對應的setter方法把屬性值傳遞給標籤處理器對象。如果標籤的屬性值是EL表達式或腳本表達式,則WEB容器首先計算表達式的值,然後把值傳遞給標籤處理器對象。
如果簡單標籤有標籤體,容器將調用setJspBody方法把代表標籤體的JspFragment對象傳遞進來。
執行標籤時:
容器調用標籤處理器的doTag()方法,開發人員在方法體內通過操作JspFragment對象,就可以實現是否執行、迭代、修改標籤體的目的。

javax.servlet.jsp.tagext.JspFragment類是在JSP2.0中定義的,它的實例對象代表JSP頁面中的一段符合JSP語法規範的JSP片段,這段JSP片段中不能包含JSP腳本元素。

WEB容器在處理簡單標籤的標籤體時,會把標籤體內容用一個JspFragment對象表示,並調用標籤處理器對象的setJspBody方法把JspFragment對象傳遞給標籤處理器對象。JspFragment類中只定義了兩個方法,如下所示:

getJspContext方法
用於返回代表調用頁面的JspContext對象.

public abstract void invoke(java.io.Writer out) 
用於執行JspFragment對象所代表的JSP代碼片段
參數out用於指定將JspFragment對象的執行結果寫入到哪個輸出流對象中,如果傳遞給參數out的值爲null,則將執行結果寫入到JspContext.getOut()方法返回的輸出流對象中。(簡而言之,可以理解爲寫給瀏覽器)

JspFragment.invoke方法可以說是JspFragment最重要的方法,利用這個方法可以控制是否執行和輸出標籤體的內容、是否迭代執行標籤體的內容或對標籤體的執行結果進行修改後再輸出。例如:
在標籤處理器中如果沒有調用JspFragment.invoke方法,其結果就相當於忽略標籤體內容;
在標籤處理器中重複調用JspFragment.invoke方法,則標籤體內容將會被重複執行;
若想在標籤處理器中修改標籤體內容,只需在調用invoke方法時指定一個可取出結果數據的輸出流對象(例如StringWriter),讓標籤體的執行結果輸出到該輸出流對象中,然後從該輸出流對象中取出數據進行修改後再輸出到目標設備,即可達到修改標籤體的目的。


下面我們在來使用簡單標籤來實現上面的四個案例:

1、是否輸出標籤體內容

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.sampletag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.JspFragment;  
  7. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  8.   
  9. /** 
  10.  * 控制標籤體是否執行 
  11.  * @author weijiang204321 
  12.  * 
  13.  */  
  14. public class SimpleTagDemo1 extends SimpleTagSupport{  
  15.   
  16.     @Override  
  17.     public void doTag() throws JspException, IOException {  
  18.         JspFragment jf = this.getJspBody();  
  19.         //相當於jf.invoke(null);  
  20.         jf.invoke(this.getJspContext().getOut());  
  21.         //這裏如果不想輸出標籤體內容的話,只需要不調用invoke方法即可  
  22.     }  
  23.   
  24. }  

我們看到這個和傳統標籤不一樣,這裏只有一個doTag方法了,在這個方法中我們通過是否調用jf.invoke方法來控制是否執行標籤體內容,這裏還有一個問題是如果我們想輸出標籤體內容,只需要調用invoke方法即可,同時這個方法傳遞的參數是一個Writer對象,所以如果我們想將標籤體內容輸出到瀏覽器中只需要傳遞out對象到這個方法即可,但是如果將這個方法的參數設置成null的話也是向瀏覽器中輸出標籤體內容的


註冊標籤體類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 是否顯示標籤體 -->  
  2.  <tag>  
  3.    <description>  
  4.        Catches any Throwable that occurs in its body and optionally  
  5.        exposes it.  
  6.    </description>  
  7.    <name>demo1</name>  
  8.    <tag-class>com.weijia.simpletag.SimpleTagDemo1</tag-class>  
  9.    <body-content>scriptless</body-content>  
  10.  </tag>  
這裏的註冊和傳統標籤不一樣的就是<body-content>標籤的值是scriptless而不是JSP了


在JSP頁面使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <simpletag:demo1>  
  2.     aaa  
  3. </simpletag:demo1>  


2、控制Jsp餘下內容是否輸出

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.sampletag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.SkipPageException;  
  7. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  8.   
  9. /** 
  10.  * 控制不執行餘下的jsp內容 
  11.  * @author weijiang204321 
  12.  * 
  13.  */  
  14. public class SimpleTagDemo2 extends SimpleTagSupport{  
  15.   
  16.     @Override  
  17.     public void doTag() throws JspException, IOException {  
  18.         //直接拋出異常就不會執行餘下的jsp內容  
  19.         throw new SkipPageException();  
  20.     }  
  21.   
  22. }  
我們只要在doTag方法中拋出一個SkipPageException異常就可以實現不執行餘下的Jsp內容


註冊標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 控制是否顯示jsp頁面 -->  
  2.  <tag>  
  3.    <description>  
  4.        Catches any Throwable that occurs in its body and optionally  
  5.        exposes it.  
  6.    </description>  
  7.    <name>demo2</name>  
  8.    <tag-class>com.weijia.simpletag.SimpleTagDemo2</tag-class>  
  9.    <body-content>empty</body-content>  
  10.  </tag>  

在Jsp頁面中使用:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <simpletag:demo2/>  
在這個標籤之後的jsp頁面內容就不會輸出了


3、標籤體重複執行

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.sampletag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.JspFragment;  
  7. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  8.   
  9. /** 
  10.  * 迭代標籤體 
  11.  * @author weijiang204321 
  12.  * 
  13.  */  
  14. public class SimpleTagDemo3 extends SimpleTagSupport{  
  15.   
  16.     @Override  
  17.     public void doTag() throws JspException, IOException {  
  18.         JspFragment jf = this.getJspBody();  
  19.         for(int i=0;i<5;i++){  
  20.             jf.invoke(null);  
  21.         }  
  22.     }  
  23.   
  24. }  
這裏就比傳統標籤的操作簡單了,直接寫在for循環中,在循環中調用invoke方法即可


註冊標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 控制標籤體重複輸出 -->  
  2. <tag>  
  3.   <description>  
  4.       Catches any Throwable that occurs in its body and optionally  
  5.       exposes it.  
  6.   </description>  
  7.   <name>demo3</name>  
  8.   <tag-class>com.weijia.simpletag.SimpleTagDemo3</tag-class>  
  9.   <body-content>scriptless</body-content>  
  10. </tag>  

在Jsp頁面中使用:
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <simpletag:demo3>  
  2.     aaaa  
  3. </simpletag:demo3>  

4、修改標籤體內容

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.sampletag;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.StringWriter;  
  5.   
  6. import javax.servlet.jsp.JspException;  
  7. import javax.servlet.jsp.tagext.JspFragment;  
  8. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  9.   
  10. /** 
  11.  * 修改標籤體 
  12.  * @author weijiang204321 
  13.  * 
  14.  */  
  15. public class SimpleTagDemo3 extends SimpleTagSupport{  
  16.   
  17.     @Override  
  18.     public void doTag() throws JspException, IOException {  
  19.         JspFragment jf = this.getJspBody();  
  20.         StringWriter sw = new StringWriter();  
  21.         jf.invoke(sw);  
  22.         String content = sw.toString();  
  23.         content = content.toUpperCase();  
  24.         this.getJspContext().getOut().write(content);  
  25.     }  
  26.   
  27. }  
我們將StringWriter對象傳遞到invoke方法中,然後再通過StringWriter對象得到標籤體內容,進行操作,然後再通過out對象輸出到瀏覽器中。


註冊標籤類:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 修改標籤體內容 -->  
  2. <tag>  
  3.   <description>  
  4.       Catches any Throwable that occurs in its body and optionally  
  5.       exposes it.  
  6.   </description>  
  7.   <name>demo4</name>  
  8.   <tag-class>com.weijia.simpletag.SimpleTagDemo4</tag-class>  
  9.   <body-content>scriptless</body-content>  
  10. </tag>  

在Jsp頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- 修改標籤體內容 -->  
  2. <simpletag:demo4>  
  3.     bbbb  
  4. </simpletag:demo4>  

這樣我們就介紹了怎樣使用傳統標籤和簡單標籤來編寫自己的標籤,這樣我們就可以將任何java代碼移到標籤類中,然後在jsp頁面中使用標籤即可。那麼最後再來看一下傳統標籤和簡單標籤的繼承關係:


1. JspTag接口

JspTag接口是所有自定義標籤的父接口,它是JSP2.0中新定義的一個標記接口,沒有任何屬性和方法。JspTag接口有Tag和SimpleTag兩個直接子接口,JSP2.0以前的版本中只有Tag接口,所以把實現Tag接口的自定義標籤也叫做傳統標籤,把實現SimpleTag接口的自定義標籤叫做簡單標籤。本書中如果沒有特別說明,自定義標籤泛指傳統標籤。

2. Tag接口

圖6.5中的Tag接口是所有傳統標籤的父接口,其中定義了兩個重要方法(doStartTag、doEndTag)方法和四個常量(EVAL_BODY_INCLUDE、SKIP_BODY、EVAL_PAGE、SKIP_PAGE),這兩個方法和四個常量的作用如下:

(1)WEB容器在解釋執行JSP頁面的過程中,遇到自定義標籤的開始標記就會去調用標籤處理器的doStartTag方法,doStartTag方法執行完後可以向WEB容器返回常量EVAL_BODY_INCLUDE或SKIP_BODY。如果doStartTag方法返回EVAL_BODY_INCLUDE,WEB容器就會接着執行自定義標籤的標籤體;如果doStartTag方法返回SKIP_BODY,WEB容器就會忽略自定義標籤的標籤體,直接解釋執行自定義標籤的結束標記。

(2)WEB容器解釋執行到自定義標籤的結束標記時,就會調用標籤處理器的doEndTag方法,doEndTag方法執行完後可以向WEB容器返回常量EVAL_PAGE或SKIP_PAGE。如果doEndTag方法返回常量EVAL_PAGE,WEB容器就會接着執行JSP頁面中位於結束標記後面的JSP代碼;如果doEndTag方法返回SKIP_PAGE,WEB容器就會忽略JSP頁面中位於結束標記後面的所有內容。

從doStartTag和doEndTag方法的作用和返回值的作用可以看出,開發自定義標籤時可以在doStartTag方法和doEndTag方法體內編寫合適的Java程序代碼來實現具體的功能,通過控制doStartTag方法和doEndTag方法的返回值,還可以告訴WEB容器是否執行自定義標籤中的標籤體內容和JSP頁面中位於自定義標籤的結束標記後面的內容。

2. IterationTag接口

IterationTag接口繼承了Tag接口,並在Tag接口的基礎上增加了一個doAfterBody方法和一個EVAL_BODY_AGAIN常量。實現IterationTag接口的標籤除了可以完成Tag接口所能完成的功能外,還能夠通知WEB容器是否重複執行標籤體內容。對於實現了IterationTag接口的自定義標籤,WEB容器在執行完自定義標籤的標籤體後,將調用標籤處理器的doAfterBody方法,doAfterBody方法可以向WEB容器返回常量EVAL_BODY_AGAIN或SKIP_BODY。如果doAfterBody方法返回EVAL_BODY_AGAIN,WEB容器就會把標籤體內容再重複執行一次,執行完後接着再調用doAfterBody方法,如此往復,直到doAfterBody方法返回常量SKIP_BODY,WEB容器纔會開始處理標籤的結束標記和調用doEndTag方法。

可見,開發自定義標籤時,可以通過控制doAfterBody方法的返回值來告訴WEB容器是否重複執行標籤體內容,從而達到循環處理標籤體內容的效果。例如,可以通過一個實現IterationTag接口的標籤來迭代輸出一個集合中的所有元素,在標籤體部分指定元素的輸出格式。

在JSP API中也提供了IterationTag接口的默認實現類TagSupport,讀者在編寫自定義標籤的標籤處理器類時,可以繼承和擴展TagSupport類,這相比實現IterationTag接口將簡化開發工作。

3. BodyTag接口

BodyTag接口繼承了IterationTag接口,並在IterationTag接口的基礎上增加了兩個方法(setBodyContent、doInitBody)和一個EVAL_BODY_BUFFERED常量。實現BodyTag接口的標籤除了可以完成IterationTag接口所能完成的功能,還可以對標籤體內容進行修改。對於實現了BodyTag接口的自定義標籤,標籤處理器的doStartTag方法不僅可以返回前面講解的常量EVAL_BODY_INCLUDE或SKIP_BODY,還可以返回常量EVAL_BODY_BUFFERED。如果doStartTag方法返回EVAL_BODY_BUFFERED,WEB容器就會創建一個專用於捕獲標籤體運行結果的BodyContent對象,然後調用標籤處理器的setBodyContent方法將BodyContent對象的引用傳遞給標籤處理器,WEB容器接着將標籤體的執行結果寫入到BodyContent對象中。在標籤處理器的後續事件方法中,可以通過先前保存的BodyContent對象的引用來獲取標籤體的執行結果,然後調用BodyContent對象特有的方法對BodyContent對象中的內容(即標籤體的執行結果)進行修改和控制其輸出。

在JSP API中也提供了BodyTag接口的實現類BodyTagSupport,讀者在編寫能夠修改標籤體內容的自定義標籤的標籤處理器類時,可以繼承和擴展BodyTagSupport類,這相比實現BodyTag接口將簡化開發工作。

4. SimpleTag接口

SimpleTag接口是JSP2.0中新增的一個標籤接口。由於傳統標籤使用三個標籤接口來完成不同的功能,顯得過於繁瑣,不利於標籤技術的推廣,因此,SUN公司爲降低標籤技術的學習難度,在JSP 2.0中定義了一個更爲簡單、便於編寫和調用的SimpleTag接口。SimpleTag接口與傳統標籤接口最大的區別在於,SimpleTag接口只定義了一個用於處理標籤邏輯的doTag方法,該方法在WEB容器執行自定義標籤時調用,並且只被調用一次。那些使用傳統標籤接口所完成的功能,例如是否執行標籤體、迭代標籤體、對標籤體內容進行修改等功能都可以在doTag方法中完成。關於SimpleTag接口的詳細介紹本書將在第7章詳細講解。

在JSP API中也提供了SimpleTag接口的實現類SimpleTagSupport,讀者在編寫簡單標籤時,可以繼承和擴展SimpleTagSupport類,這相比實現SimpleTag接口將簡化開發工作。

 

爲方便讀者日後查詢傳統標籤接口中的各個方法可以返回的返回值,筆者在表6.1列舉了Tag接口、IterationTag接口和BodyTag接口中的主要方法及它們分別可以返回的返回值的說明。



下面我們來看一下如何開發一個具有屬性的自定義標籤的內容:

要想讓一個自定義標籤具有屬性,通常需要完成兩個任務:
在標籤處理器中編寫每個屬性對應的setter方法
在TLD文件中描術標籤的屬性
爲自定義標籤定義屬性時,每個屬性都必須按照JavaBean的屬性命名方式,在標籤處理器中定義屬性名對應的setter方法,用來接收JSP頁面調用自定義標籤時傳遞進來的屬性值。 例如屬性url,在標籤處理器類中就要定義相應的setUrl(String url)方法。
在標籤處理器中定義相應的set方法後,JSP引擎在解析執行開始標籤前,也就是調用doStartTag方法前,會調用set屬性方法,爲標籤設置屬性。

在TLD文件中的描述規格是爲:

<tag>元素的<attribute>子元素用於描述自定義

標籤的一個屬性,自定義標籤所具有的每個屬性

都要對應一個<attribute>元素 。

<attribute>

  <description>description</description>

  <name>aaaa</name>

  <required>true</required>

  <rtexprvalue>true</rtexprvalue>

  <type>ObjectType</type>

</attribute>


其中的各個屬性值的含義如下:



那麼下面就來看一個實例,通過一個屬性值來控制標籤體的內容輸出的次數:

自定義標籤類:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.propertytag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.JspFragment;  
  7. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  8.   
  9. public class PropertyTag extends SimpleTagSupport{  
  10.   
  11.     private int count = 0;  
  12.       
  13.     public void setCount(int count){  
  14.         this.count = count;  
  15.     }  
  16.       
  17.     @Override  
  18.     public void doTag() throws JspException, IOException {  
  19.         JspFragment jf = this.getJspBody();  
  20.         for(int i=0;i<count;i++){  
  21.             jf.invoke(null);  
  22.         }  
  23.     }  
  24.   
  25. }  
這裏需要定義一個變量來記錄執行的次數,同時還需要提供set方法


註冊這個標籤:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <tag>  
  2.     <description>  
  3.         Catches any Throwable that occurs in its body and optionally  
  4.         exposes it.  
  5.     </description>  
  6.     <name>demo</name>  
  7.     <tag-class>com.weijia.propertytag.PropertyTag</tag-class>  
  8.     <body-content>scriptless</body-content>  
  9.     <attribute>  
  10.         <description>  
  11.             Name of the exported scoped variable for the  
  12.             exception thrown from a nested action. The type of the  
  13.             scoped variable is the type of the exception thrown.  
  14.         </description>  
  15.         <name>count</name>  
  16.         <required>true</required>  
  17.         <rtexprvalue>true</rtexprvalue>  
  18.     </attribute>  
  19.   </tag>  
我們這裏設置這個屬性的名稱是count,而且這個屬性在標籤中是必須設置的,同時這個標籤可以使用表達式


在Jsp頁面中使用:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <propertytag:demo count="9">  
  2.     aaaaa  
  3. </propertytag:demo>  
在頁面中輸出9次aaaaa


雖然我們這裏看到了輸出的很簡單,設置也很簡單,但是這裏面還是有很多內容的

首先來看一下,我們在Jsp頁面中輸入的是字符串,但是我們定義的count是int類型,沒有報錯,所以這裏他做了類型轉換,當然這個不是能夠轉換所有的類型的,只能轉化8中基本類型,比如我們定義了一個屬性是Date類型的,當我們在Jsp頁面中傳遞"1990-08-01"這樣就會報錯的,當然我們可以使用腳本表達式進行屬性的賦值是可以的,比如:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <propertytag:demo count="<%=new Date()%>">  
  2.     aaaaa  
  3. </propertytag:demo>  

在來看一下,他是怎麼定位到屬性count的,這個其實在學習Java基礎知識的時候就說過,在學習JavaBean的相關知識的時候,我們知道一個Bean對象的屬性的概念,比如這裏我們定義了一個count變量,同時設置了他的set方法,那麼這個count就是一個屬性,但是屬性的概念不是通過變量名來定義的,而是通過set方法來定義的,比如我們這裏可以將count變量名改成counts,但是setCount方法名不變,我們運行程序,仍然不會報錯的,但是我們將setCount方法名改成setCounts的時候,運行程序就報錯了,原因也很好理解,他在進行變量count進行設置值的時候,會通過set方法來進行設置,這時候就會通過setXXX來找到相對應的set方法,從而能夠對每個變量的值設置正確。這個相關內容其實我們在之前介紹<jsp:setProperty>標籤的時候講到過,這個技術在JavaWeb中很常用的,專門用來操作Bean對象的(內省技術BeanUtils)


介紹完自定義標籤的屬性的相關知識後,接下來我們就來看看JSTL給我們提供的標籤庫,JSTL標籤庫可以分爲以下幾種:

1.核心標籤庫 
2.國際化標籤
3.數據庫標籤 
4.XML標籤
5.JSTL函數(EL函數)

現在用到最多的就是核心標籤庫和JSTL函數庫了,其他的三種標籤不是很常用(幾乎拋棄),所以這裏就不做太多的介紹。


下面就先來看一下JSTL的核心標籤庫了,我們在使用JSTL標籤庫的時候需要導入兩個jar:jstl.jar和standard.jar

我們在導入包之後我們可以查看他的tld標籤描述文檔的:


我們看到他的核心庫是c.tld,函數庫是fn.tld,這樣我們就可以通過這些標籤描述文檔中查找到有哪些標籤可以使用,以及使用的方法


我們看到了c.tld標籤說明文件的uri是http://java.sun.com/jsp/jstl/core,所以我們如果要使用這個標籤的話就只要在jsp中引入即可:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>  
前期工作搞定了,下面就來詳細看一下每個標籤的具體使用方法:

1、c:out標籤的使用:

<c:out> 標籤用於輸出一段文本內容到pageContext對象當前保存的“out”對象中


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:out標籤 -->  
  2.     <!-- 輸出給瀏覽器,但是沒必要這樣輸出是沒意義的,可以直接輸出的aaaaaa -->  
  3.     <c:out value="aaaa"></c:out>  
  4.     <!-- 轉義之後輸出/默認值,這樣c:out標籤纔有意義-->  
  5.     <c:out value="<a href=''>點點</a>" default="aaa" escapeXml="true"></c:out>  
  6.     <%  
  7.         request.setAttribute("data","xxx");  
  8.      %>  
  9.      <c:out value="${data}" default="aaa"></c:out>  
這個標籤很簡單的,就是就是向瀏覽器中直接輸出內容的,那麼這個和使用EL表達式輸出有什麼區別呢,他有什麼特別的好處呢?

他的好處就在於default和escapeXml這兩個屬性的使用,default屬性可以設置輸出的默認值,我們知道EL表達式在各個域中如果找不到屬性值就會輸出空字符串,但是我們通過這個屬性就可以設置當從所有的域中找不到相應的屬性值,就會輸出默認值,同時還有一個escapeXml這個屬性值,這個屬性進行輸出內容進行html轉義,我們之前都是通過一個方法進行轉義的:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private String htmlFilter(String message){  
  2.     if(message == null){  
  3.         return null;  
  4.     }  
  5.       
  6.     char[] content = new char[message.length()];  
  7.     message.getChars(0, message.length(), content, 0);  
  8.     StringBuffer result = new StringBuffer(message.length()+50);  
  9.     for(int i=0;i<content.length;i++){  
  10.         switch(content[i]){  
  11.         case '<':  
  12.             result.append("<");  
  13.             break;  
  14.         case '>':  
  15.             result.append(">");  
  16.             break;  
  17.         case '&':  
  18.             result.append("&");  
  19.             break;  
  20.         case '"':  
  21.             result.append(""");  
  22.             break;  
  23.         default:  
  24.             result.append(content[i]);  
  25.         }  
  26.     }  
  27.     return result.toString();  
  28. }  
只要設置這個屬性值爲true的話,我們就不需要手動的進行轉義了,所以說這個標籤還是有他特定的功能的,可不能忘記他呀!


2、c:set標籤

<c:set>標籤用於把某一個對象存在指定的域範圍內,或者設置Web域中的java.util.Map類型的屬性對象或JavaBean類型的屬性對象的屬性


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:set標籤 -->  
  2.     <!-- 向page域中存入到xxx -->  
  3.     <c:set var="data" value="xxx" scope="page"/>  
  4.     ${data}  
  5.     <!-- 向map中存入數據 -->  
  6.     <%  
  7.         Map map = new HashMap();  
  8.         request.setAttribute("map",map);  
  9.      %>  
  10.      <c:set property="name" value="uuu" target="${map}"/>  
  11.      ${map.name}  
  12.      <!-- 向javabean中存入數據 -->  
  13.      <%  
  14.         Person p = new Person();  
  15.         request.setAttribute("p",p);  
  16.       %>  
  17.      <c:set property="name" value="uuu" target="${p}"/>  
  18.      ${p.name}  
這個標籤可以指定在四個域中設置屬性值,同時設置的對象不僅只有基本類型,還可以設置對象類型,集合類型


3、c:remove標籤

這個標籤可以在四個域中刪除指定的屬性

其語法格式如下:
<c:remove var="varName" 
[scope="{page|request|session|application}"] /> 

用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:remove標籤 -->  
  2.   <!-- 刪除屬性 -->  
  3.   <%  
  4.     request.setAttribute("data","xxx");  
  5.   %>  
  6.   <c:remove var="data" scope="request"/>  

4、c:catch標籤

<c:catch>標籤用於捕獲嵌套在標籤體中的內容拋出的異常,

其語法格式如下:

<c:catch [var="varName"]>nested actions</c:catch>
var屬性用於標識<c:catch>標籤捕獲的異常對象,它將保存在page這個Web域中

用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:catch異常捕獲 -->  
  2.      <!--var是存入異常對象的關鍵字 -->  
  3.      <c:catch var="myex">  
  4.      <%  
  5.         int x = 1/0;  
  6.      %>  
  7.      </c:catch>  
  8.      <!-- 異常對象必須要有message屬性 -->  
  9.      ${myex.message}  

5、c:if標籤

這個標籤是控制標籤內容的輸出


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:if -->  
  2.     <!-- 將判斷結果以aaa爲關鍵字存入到域中 -->  
  3.     <%  
  4.         request.setAttribute("user",null);  
  5.      %>  
  6.      ${user}  
  7.     <c:if var="aaa" test="${user == null}",scope="page"/>  
  8.      ${aaa}   
同時可以將判斷條件的值使用變量存起來


6、c:choose/c:when/c:other標籤

這三個標籤是一起使用的,實現效果和if...else是一樣的

用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:choose標籤 -->  
  2. <c:choose>  
  3.     <c:when test="${true}">  
  4.         aaaa  
  5.     </c:when>  
  6.     <c:otherwise>  
  7.         bbb  
  8.     </c:otherwise>  
  9. </c:choose>  

7、c:forEach標籤

這個標籤是用來迭代數據的,之前用過這個標籤,但是他還有很多強大的功能:


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:forEach -->  
  2.     <%  
  3.         List list = new ArrayList();  
  4.         list.add("aaa");  
  5.         list.add("bbb");  
  6.         list.add("ccc");  
  7.         list.add("ddd");  
  8.         request.setAttribute("list",list);  
  9.      %>  
  10.       
  11.     <c:forEach var="str" items="${list}">  
  12.         ${str}  
  13.     </c:forEach>  
  14.       
  15.     <!-- 設置步長,分頁功能 -->  
  16.     <c:forEach  var="num" items="${list}" begin="1" end="9" step="1">  
  17.         ${num}  
  18.     </c:forEach>  
  19.       
  20.     <!-- 記錄迭代變量的值 -->  
  21.     <c:forEach var="str" items="${list}" varStatus="status">  
  22.         ${status.count}  
  23.     </c:forEach>  

這裏我們通過這個標籤迭代輸出list集合中的數據,這個功能是最基礎的,也是最簡單的

他還有一些屬性可以設置迭代的開始位置和結束位置以及迭代的步長信息

同時還有一個屬性varStatus可以記錄當前迭代信息,他保存的是一個對象,但是這個對象有以下的屬性值:


這個屬性的功能我們可以實現表格中的奇數和偶數行的不同顯示


8、c:param標籤

這個標籤是設置參數值的,這個標籤是不能單獨使用的,他是結合c:url或者c:redirect標籤使用


9、c:url標籤

這個標籤可以實現url重寫(在介紹Session的時候)和url編碼


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <!-- c:url標籤 -->  
  2.     <!-- url重寫 -->  
  3.     <c:url var="url" value="JspDemo/1.jsp"/>  
  4.         <a href='${url}'>購買</a>  
  5.     </c:url>  
  6.     <!-- url標籤直接輸出url -->  
  7.     <a href='<c:url value="/1.jsp"'>點點</a>  
  8.     <!-- c:url構建參數(自動url編碼) -->  
  9.     <c:url var="index" value="/1.jsp">  
  10.         <c:param name="name" value="中國"/>  
  11.     </c:url>  

10.c:redirect標籤

這個標籤是用來實現重定向的


用法:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <c:redirect url="JspDemo/1.jsp">  
  2.     <c:param name="name" value="jiangwei"></c:param>  
  3. </c:redirect>  
我們在之前介紹的jsp標籤中只有<jsp:forword>轉發標籤,而沒有重定向的標籤,那麼這個就有重定向的標籤了。

以上我們介紹了JSTL中的核心標籤庫的相關知識,下面再來看一下JSTL的函數庫,其實這部分內容在我們之前的EL表達式一篇文章中的最後部分作了詳細講解:http://blog.csdn.net/jiangwei0910410003/article/details/23748131


下面還有一個重要的內容就是我們要通過我們上面學習到的自定義標籤的知識來開發一套類似於JSTL的標籤庫,並將其進行打包,給其他項目使用,這裏我們就是用簡單標籤了,而不是用傳統標籤。

1、開發if標籤

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.iftag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  7.   
  8. public class IfTag extends SimpleTagSupport{  
  9.   
  10.     private boolean test;  
  11.       
  12.     public void setTest(boolean test){  
  13.         this.test = test;  
  14.     }  
  15.       
  16.     @Override  
  17.     public void doTag() throws JspException, IOException {  
  18.         if(test){  
  19.             this.getJspBody().invoke(null);  
  20.         }  
  21.     }  
  22.   
  23. }  
我們定義一個boolean類型的test變量來保存判斷值,然後通過這個判斷值來控制是否輸出標籤體內容


2、開發choose/when/otherwise標籤,在開發這套標籤的時候,我們會發現遇到一個難處就是多個標籤之間需要進行通信,比如說一個標籤執行了標籤體內容,那麼其他標籤體的內容就不能執行了,所以這裏需要給多個標籤體外面在套一個父標籤(這也是一個父標籤開發的案例),通過那麼每個子標籤可以通過父標籤中的一個變量來判斷是否執行自己的標籤體,原理就是這樣的,下面是實現類,其中ChooseTag是父標籤了,WhenTag和OtherWiseTag是子標籤,同時給WhenTag標籤定義一個boolean屬性來接收外界的判斷條件

ChooseTag:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.choosetag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  7.   
  8. public class ChooseTag extends SimpleTagSupport{  
  9.       
  10.     private boolean isDo;  
  11.   
  12.     public boolean isDo() {  
  13.         return isDo;  
  14.     }  
  15.   
  16.     public void setDo(boolean isDo) {  
  17.         this.isDo = isDo;  
  18.     }  
  19.   
  20.     @Override  
  21.     public void doTag() throws JspException, IOException {  
  22.         this.getJspBody().invoke(null);  
  23.     }  
  24.   
  25. }  

WhenTag:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.choosetag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  7.   
  8. public class WhenTag extends SimpleTagSupport {  
  9.   
  10.     private boolean test;  
  11.       
  12.     public void setTest(boolean test){  
  13.         this.test = test;  
  14.     }  
  15.       
  16.     @Override  
  17.     public void doTag() throws JspException, IOException {  
  18.         //獲取到父標籤  
  19.         ChooseTag parentTag = (ChooseTag)this.getParent();  
  20.         if(test && !parentTag.isDo()){  
  21.             this.getJspBody().invoke(null);  
  22.             parentTag.setDo(true);  
  23.         }  
  24.     }  
  25.   
  26. }  

OtherWiseTag:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.choosetag;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.jsp.JspException;  
  6. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  7.   
  8. public class OtherWiseTag extends SimpleTagSupport{  
  9.   
  10.     @Override  
  11.     public void doTag() throws JspException, IOException {  
  12.         ChooseTag parentTag = (ChooseTag)this.getParent();  
  13.         if(parentTag.isDo()){  
  14.             this.getJspBody().invoke(null);  
  15.         }  
  16.     }  
  17.   
  18. }  


同時需要在tld文件中對這三個標籤類進行描述:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <tag>  
  2.     <name>choose</name>  
  3.     <tag-class>com.weijia.choosetag.ChooseTag</tag-class>  
  4.     <body-content>scriptless</body-content>  
  5.   </tag>  
  6.     
  7.   <tag>  
  8.     <name>when</name>  
  9.     <tag-class>com.weijia.choosetag.WhenTag</tag-class>  
  10.     <body-content>scriptless</body-content>  
  11.     <attribute>  
  12.         <name>test</name>  
  13.         <required>true</required>  
  14.         <rtexprvalue>true</rtexprvalue>  
  15.     </attribute>  
  16.   </tag>  
  17.     
  18.   <tag>  
  19.     <name>otherwise</name>  
  20.     <tag-class>com.weijia.choosetag.OtherWiseTag</tag-class>  
  21.     <body-content>scriptless</body-content>  
  22.   </tag>  


3、for:Each標籤:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package com.weijia.foreach;  
  2.   
  3. import java.io.IOException;  
  4. import java.lang.reflect.Array;  
  5. import java.util.ArrayList;  
  6. import java.util.Collection;  
  7. import java.util.Map;  
  8.   
  9. import javax.servlet.jsp.JspException;  
  10. import javax.servlet.jsp.tagext.SimpleTagSupport;  
  11.   
  12. import sun.text.CompactShortArray.Iterator;  
  13.   
  14. /** 
  15.  * ForEach標籤 
  16.  * @author weijiang204321 
  17.  * 
  18.  */  
  19. public class ForEachTag extends SimpleTagSupport{  
  20.   
  21.     private Object items;  
  22.     private String var;  
  23.     private Collection collection;  
  24.       
  25.     public void setItems(Object items) {  
  26.         this.items = items;  
  27.         if(items instanceof Collection){  
  28.             collection = (Collection)items;  
  29.         }  
  30.           
  31.         if(items instanceof Map){  
  32.             Map map = (Map) items;  
  33.             collection = map.entrySet();  
  34.         }  
  35.         //這種判斷數組的方式是不適合基本類型數組的,  
  36.         //同時Arrays.asList方法接收的是可變參數Object...所以對於基本類型的數組是沒有效果的  
  37.         /*if(items instanceof Object[]){ 
  38.             Object[] obj = (Object[])items; 
  39.             collection = Arrays.asList(obj); 
  40.         }*/  
  41.         //使用反射技術可以判斷基本類型的數據類型  
  42.         if(items.getClass().isArray()){  
  43.             int len = Array.getLength(items);  
  44.             collection = new ArrayList();  
  45.             for(int i=0;i<len;i++){  
  46.                 collection.add(Array.get(items, i));  
  47.             }  
  48.         }  
  49.     }  
  50.   
  51.     public void setVar(String var) {  
  52.         this.var = var;  
  53.     }  
  54.   
  55.     @Override  
  56.     public void doTag() throws JspException, IOException {  
  57.         Iterator it = (Iterator) collection.iterator();  
  58.         while(it.hasNext()){  
  59.             Object value = it.next();  
  60.             this.getJspContext().setAttribute(var, value);  
  61.         }  
  62.         this.getJspBody().invoke(null);  
  63.     }  
  64.   
  65. }  


這個是實現了forEach標籤的,在tld文件中進行描述一下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <tag>  
  2.   <name>forEach</name>  
  3.   <tag-class>com.weijia.foreach.ForEachTag</tag-class>  
  4.   <body-content>scriptless</body-content>  
  5.   <attribute>  
  6.       <name>var</name>  
  7.       <required>true</required>  
  8.       <rtexprvalue>true</rtexprvalue>  
  9.   </attribute>  
  10.   <attribute>  
  11.       <name>items</name>  
  12.       <required>true</required>  
  13.       <rtexprvalue>true</rtexprvalue>  
  14.   </attribute>  
  15. </tag>  

關於這個forEach標籤的知識我們要好好的解釋一下,因爲這裏面有很多需要注意的地方, 這裏我們定義了一個Object類型的items,這個變量是用來接收迭代對象的,還定義了一個String類型的var,這個是用來將每次迭代之後的值存入到域中的key名稱。還定義了一個Collection類型的變量,這個變量只是一個輔助的變量,用來將迭代對象轉化成集合類型(map類型可以轉換、數組也可以轉化)。這樣我們就可以統一進行處理了。

我們看到setItems方法中,我們首先判斷這個迭代對象是不是集合類型的,是的話,直接賦值到collections變量,如果不是,在判斷是不是Map類型的,如果是的話,就將Map類型的變量轉化成collections,如果不是,在判斷是不是數組對象Object[],是的話,就進行轉化,這裏使用了Arrays.asList(T...)這個方法,關於這個方法,我們看到他的參數是一個可變的對象類型參數,看着這個樣的判斷是可以了,涵蓋了所有的迭代對象類型,但是我們其實發現了一個問題,那就是在最後一次判斷數組的時候,我們發現這個是對象類型的數組,那麼我們如果傳遞基本類型數組的話,會是什麼樣的情況呢?其實我們知道基本類型數組其實就是一個Object對象,所以不是Object[],那麼這裏的涵蓋的範圍就有問題了,我們這裏還需要單獨的判斷基本類型的數組,然後進行操作,那麼我們來看一下jstl中的forEach標籤的定義吧:

我們將standard.jar進行解壓,然後找到forEach標籤的定義類:我們可以從c.tld文件中找到forEach對應的類:

org.apache.taglibs.standard.tag.rt.core.ForEachTag,這時候我們需要去下載一個反編譯工具,能夠查看class文件的,叫做:jd-gui.exe

然後通過這個工具打開ForEachTag.class:

[java] view plaincopy
  1. package org.apache.taglibs.standard.tag.rt.core;  
  2.   
  3. import java.util.ArrayList;  
  4. import javax.servlet.jsp.JspTagException;  
  5. import javax.servlet.jsp.jstl.core.LoopTag;  
  6. import javax.servlet.jsp.tagext.IterationTag;  
  7. import org.apache.taglibs.standard.tag.common.core.ForEachSupport;  
  8.   
  9. public class ForEachTag extends ForEachSupport  
  10.   implements LoopTag, IterationTag  
  11. {  
  12.   public void setBegin(int paramInt)  
  13.     throws JspTagException  
  14.   {  
  15.     this.beginSpecified = true;  
  16.     this.begin = paramInt;  
  17.     validateBegin();  
  18.   }  
  19.   
  20.   public void setEnd(int paramInt)  
  21.     throws JspTagException  
  22.   {  
  23.     this.endSpecified = true;  
  24.     this.end = paramInt;  
  25.     validateEnd();  
  26.   }  
  27.   
  28.   public void setStep(int paramInt)  
  29.     throws JspTagException  
  30.   {  
  31.     this.stepSpecified = true;  
  32.     this.step = paramInt;  
  33.     validateStep();  
  34.   }  
  35.   
  36.   public void setItems(Object paramObject)  
  37.     throws JspTagException  
  38.   {  
  39.     if (paramObject == null)  
  40.       this.rawItems = new ArrayList();  
  41.     else  
  42.       this.rawItems = paramObject;  
  43.   }  
  44. }  


我們發現ForEach實現了IterationTag接口,我們在前面看到傳統標籤和簡單標籤類結構系統圖中看到,這個適用於迭代輸出標籤體內容的接口,而且這個是傳統標籤,可見jstl中的ForEach標籤是使用傳統標籤來實現的,我們知道如果是迭代的話,會實現相應的迭代方法,但是我們發現ForEachTag類中只有get/set方法,所以我們這時候可以查看他的父類ForEachSupport中核心的方法:

[java] view plaincopy
  1. protected ForEachIterator supportedTypeForEachIterator(Object paramObject)  
  2.     throws JspTagException  
  3.   {  
  4.     ForEachIterator localForEachIterator;  
  5.     if ((paramObject instanceof Object[]))  
  6.       localForEachIterator = toForEachIterator((Object[])paramObject);  
  7.     else if ((paramObject instanceof boolean[]))  
  8.       localForEachIterator = toForEachIterator((boolean[])paramObject);  
  9.     else if ((paramObject instanceof byte[]))  
  10.       localForEachIterator = toForEachIterator((byte[])paramObject);  
  11.     else if ((paramObject instanceof char[]))  
  12.       localForEachIterator = toForEachIterator((char[])paramObject);  
  13.     else if ((paramObject instanceof short[]))  
  14.       localForEachIterator = toForEachIterator((short[])paramObject);  
  15.     else if ((paramObject instanceof int[]))  
  16.       localForEachIterator = toForEachIterator((int[])paramObject);  
  17.     else if ((paramObject instanceof long[]))  
  18.       localForEachIterator = toForEachIterator((long[])paramObject);  
  19.     else if ((paramObject instanceof float[]))  
  20.       localForEachIterator = toForEachIterator((float[])paramObject);  
  21.     else if ((paramObject instanceof double[]))  
  22.       localForEachIterator = toForEachIterator((double[])paramObject);  
  23.     else if ((paramObject instanceof Collection))  
  24.       localForEachIterator = toForEachIterator((Collection)paramObject);  
  25.     else if ((paramObject instanceof Iterator))  
  26.       localForEachIterator = toForEachIterator((Iterator)paramObject);  
  27.     else if ((paramObject instanceof Enumeration))  
  28.       localForEachIterator = toForEachIterator((Enumeration)paramObject);  
  29.     else if ((paramObject instanceof Map))  
  30.       localForEachIterator = toForEachIterator((Map)paramObject);  
  31.     else if ((paramObject instanceof String))  
  32.       localForEachIterator = toForEachIterator((String)paramObject);  
  33.     else  
  34.       localForEachIterator = toForEachIterator(paramObject);  
  35.     return localForEachIterator;  
  36.   }  


我們發現他會對每個傳遞進來的對象進行判斷,然後進行一些操作。同時對基本類型進行判斷,但是我們發現這樣的代碼是有點不好看,我們爲了體現出我們的技術,我們這裏可以將代碼改一下,並且是實現的比他還要好,就是以下的代碼段:

[java] view plaincopy
  1. //使用反射技術可以判斷基本類型的數據類型  
  2. if(items.getClass().isArray()){  
  3.     int len = Array.getLength(items);  
  4.     collection = new ArrayList();  
  5.     for(int i=0;i<len;i++){  
  6.         collection.add(Array.get(items, i));  
  7.     }  
  8. }  
這裏我們使用反射技術來判斷是不是數組類型,這裏可以判斷是基本類型數組還是對象類型數組,然後再使用Array這個工具類進行操作數組中的元素,這樣我們看到這樣的代碼就比jstl中的代碼簡介明瞭,而且技術上也體現出一點高超。


下面我們在tld文件中進行描述一下:

[html] view plaincopy
  1. <tag>  
  2.     <name>forEach</name>  
  3.     <tag-class>com.weijia.foreach.ForEachTag</tag-class>  
  4.     <body-content>scriptless</body-content>  
  5.     <attribute>  
  6.         <name>var</name>  
  7.         <required>true</required>  
  8.         <rtexprvalue>true</rtexprvalue>  
  9.     </attribute>  
  10.     <attribute>  
  11.         <name>items</name>  
  12.         <required>true</required>  
  13.         <rtexprvalue>true</rtexprvalue>  
  14.     </attribute>  

在Jsp頁面中使用:

[html] view plaincopy
  1. <span>  </span><%  
  2.         List list = new ArrayList();  
  3.         list.add("aaa");  
  4.         list.add("bbb");  
  5.         String[] strAry = new String[]{"aaa","bbb","ccc"};  
  6.         int[] intAry = new int[]{1,2,3,6};  
  7.      %>  
  8.       
  9.     <c:forEach var="item" items="<%=list%>">  
  10.         ${item}  
  11.     </c:forEach>  
  12.       
  13.     <br>  
  14.       
  15.     <c:forEach var="item" items="<%=strAry%>">  
  16.         ${item}  
  17.     </c:forEach>  
  18.       
  19.     <br>  
  20.       
  21.     <c:forEach var="item" items="<%=intAry%>">  
  22.         ${item}  
  23.     </c:forEach>  

我們使用Java代碼模擬一個集合,對象類型的數組,基本類型數組,然後進行顯示,顯示結果


在這裏額外的插一句:我在做這個實驗的時候犯了一個很低級的錯誤,就是在使用標籤的時候,給items賦值的時候我已開始使用的是EL表達式(${list}),然後總是報空指針異常,糾結了好長時間,發現items是null,那麼就是沒有傳遞對象給他,後來發現EL表達式是從域中取數據的,我們沒有將list存入到任何域中,所以肯定拿不到了,這時候改用腳本表達式就可以了,因爲腳本表達式就是可以去取頁面中腳本片段中定義的變量值的,所以最後發現這個錯誤真的很低級的!!!


以上就是我們實現了類似於jstl中的標籤庫的一些標籤的功能,這裏我們可以聯繫一下我們之前在開始介紹自定標籤的時候實現的四個案例:

1、控制標籤體是否輸出

2、控制標籤體重複輸出

3、控制餘下的Jsp頁面是否顯示

4、修改標籤體內容

其實我們會發現,上面實現的If標籤其實就是第一個案例的體現,choose/when/otherwise標籤就是第一個案例的體現,但是這裏面還有一個功能就是父標籤的編寫,forEach標籤是第二個案例的實現以及第四個案例的實現。所以說我們爲什麼一開始要介紹那四個案例,其實是爲這部分內容做鋪墊的。


下面我們來進行打包操作了,我們需要將我們定義的tld文件一起打包,這個打包也是很簡單的,我們只需要新建一個Java項目,將我們定義好的標籤類都拷貝過去,同時在項目中新建一個META-INF文件夾,在將我們定義的的tld文件拷貝進去,雖然會提示很多錯誤(因爲是Java項目,不是Web項目很多類是找不到的)但是我們不理會,因爲我們知道我們的類的邏輯和語法是沒有錯誤的,只是找不到相應的類,這時候我們進行打包,一定要將META-INF文件夾一起打包進去,這時候我們就可以使用這個我們自己定義的標籤庫包了。


總結:好了,JSTL和自定義標籤的相關知識就介紹到這裏的,同時我們JavaWeb學習篇也到這裏就結束了,這一系列的文章寫了半個月吧,從中學習到了很多的知識,在此將每個知識點整理出來供大家分享,如果有什麼不正確的地方還請提出,我立即做出修改。謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章