自定義標簽是Jav
aWeb的一部分非常重要的核心功能,我們之前就說過,JSP規范說的很清楚,就是Jsp頁面中禁止編寫一行Java代碼,就是最好不要有Java腳本片段,下面就來看一下自定義標簽的簡介:
自定義標簽主要用於移除Jsp頁面中的java代碼。
移除jsp頁面中的java代碼,只需要完成兩個步驟:
編寫一個實現Tag接口的Java類,並覆蓋doStartTag方法,把jsp頁面中的java代碼寫到doStartTag方法中。
編寫標簽庫描述符(tld)文件,在tld文件中對自定義標簽進行描述。
完成以上操作,即可在JSP頁面中導入和使用自定義標簽。
快速入門:使用自定義標簽輸出客戶機IP
查看tag接口api文檔,分析自定義標簽的執行流程。
下面來看一下一個簡單的Demo使用自定義標簽打印客戶機的IP地址
首先我們自定義標簽類:ViewIpTag
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
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了,格式如下:
<%@ 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中使用標簽:
客戶機的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代碼:
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中的方法的代碼:
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、不執行標簽體內容
自定義標簽類:
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常量,就不會執行標簽體內容,代碼很簡單。
下面我們再來注冊這個標簽類:
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頁面中使用:
1 <!-- 不執行標簽體 --> 2 <simpletag:demo1> 3 aaaa 4 </simpletag:demo1>
2、控制JSP余下頁面的內容不執行
自定義標簽類:
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文件中注冊這個自定義標簽類:
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頁面中使用:
1 <!-- 不執行余下的頁面內容 --> 2 <simpletag:demo2/>
這樣使用之后,在這個標簽之后的內容就不會執行了,我們在上面分析源代碼的時候已經解析過了。頁面都不會含有余下的內容了,如果我們將這個標簽放在頁面的第一行,那么這個頁面就是一片空白,我們在瀏覽器中查看頁面的源代碼,也是發現一片空白的,因為out對象沒有進行print了
3、重復執行標簽體內容
自定義標簽體類:
package com.weijia.traditionaltag; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; /** * 控制標簽體重復執行 * @author weijiang204321 * */ public class TagDemo3 extends TagSupport{ private int count = 5; @Override public int doStartTag() throws JspException { return TagSupport.EVAL_BODY_INCLUDE; } @Override public int doAfterBody() throws JspException { count--; if(count > 0){ return TagSupport.EVAL_BODY_AGAIN;//執行完之后接着執行doAfterBody()方法 }else{ return TagSupport.SKIP_BODY; } } @Override public int doEndTag() throws JspException { return TagSupport.SKIP_BODY; } }
這里我們需要在doAfterBody方法中操作了,因為這個方法的返回值為TagSupport.EVAL_BODY_AGAIN常量的話,這個方法還會被調用,直到這個方法返回TagSupport.SKIP_BODY,所以我們這里控制標簽體內容執行5次,我們定義一個變量就可以了,然后控制doAfterBody方法的返回值,這里還要注意的是,在doStartTag方法中返回值是TagSupport.EVAL_BODY_INCLUDE常量,因為我們要執行標簽體內容的。
注冊自定義標簽類:
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頁面中使用:
<!-- 重復執行標簽體內容 --> <simpletag:demo3> aaaa </simpletag:demo3>
這時候在頁面中就會輸出5個aaaa
4、修改標簽體內容
自定義標簽類:
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方法中取出標簽體內容然后進行操作之后再寫到瀏覽器中。
注冊我們的自定義標簽類:
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頁面中使用:
<!-- 修改標簽體內容 --> <simpletag:demo4> bbbb </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、是否輸出標簽體內容
自定義標簽類:
package com.weijia.sampletag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 控制標簽體是否執行 * @author weijiang204321 * */ public class SimpleTagDemo1 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); //相當於jf.invoke(null); jf.invoke(this.getJspContext().getOut()); //這里如果不想輸出標簽體內容的話,只需要不調用invoke方法即可 } }
我們看到這個和傳統標簽不一樣,這里只有一個doTag方法了,在這個方法中我們通過是否調用jf.invoke方法來控制是否執行標簽體內容,這里還有一個問題是如果我們想輸出標簽體內容,只需要調用invoke方法即可,同時這個方法傳遞的參數是一個Writer對象,所以如果我們想將標簽體內容輸出到瀏覽器中只需要傳遞out對象到這個方法即可,但是如果將這個方法的參數設置成null的話也是向瀏覽器中輸出標簽體內容的
注冊標簽體類:
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頁面使用:
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內容
注冊標簽類:
<!-- 控制是否顯示jsp頁面 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo2</name> <tag-class>com.weijia.simpletag.SimpleTagDemo2</tag-class> <body-content>empty</body-content> </tag>
在Jsp頁面中使用:
<simpletag:demo2/>
在這個標簽之后的jsp頁面內容就不會輸出了
3、標簽體重復執行
自定義標簽類:
package com.weijia.sampletag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.JspFragment; import javax.servlet.jsp.tagext.SimpleTagSupport; /** * 迭代標簽體 * @author weijiang204321 * */ public class SimpleTagDemo3 extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); for(int i=0;i<5;i++){ jf.invoke(null); } } }
這里就比傳統標簽的操作簡單了,直接寫在for循環中,在循環中調用invoke方法即可
注冊標簽類:
<!-- 控制標簽體重復輸出 --> <tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo3</name> <tag-class>com.weijia.simpletag.SimpleTagDemo3</tag-class> <body-content>scriptless</body-content> </tag>
在Jsp頁面中使用:
1 <simpletag:demo3> 2 aaaa 3 </simpletag:demo3>
4、修改標簽體內容
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對象輸出到瀏覽器中。
注冊標簽類:
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 plain copy <!-- 修改標簽體內容 --> <simpletag:demo4> bbbb </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>
其中的各個屬性值的含義如下:
那么下面就來看一個實例,通過一個屬性值來控制標簽體的內容輸出的次數:
自定義標簽類:
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方法
注冊這個標簽:
<tag> <description> Catches any Throwable that occurs in its body and optionally exposes it. </description> <name>demo</name> <tag-class>com.weijia.propertytag.PropertyTag</tag-class> <body-content>scriptless</body-content> <attribute> <description> Name of the exported scoped variable for the exception thrown from a nested action. The type of the scoped variable is the type of the exception thrown. </description> <name>count</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
我們這里設置這個屬性的名稱是count,而且這個屬性在標簽中是必須設置的,同時這個標簽可以使用表達式
在Jsp頁面中使用:
1 <propertytag:demo count="9"> 2 aaaaa 3 </propertytag:demo>
在頁面中輸出9次aaaaa
雖然我們這里看到了輸出的很簡單,設置也很簡單,但是這里面還是有很多內容的
首先來看一下,我們在Jsp頁面中輸入的是字符串,但是我們定義的count是int類型,沒有報錯,所以這里他做了類型轉換,當然這個不是能夠轉換所有的類型的,只能轉化8中基本類型,比如我們定義了一個屬性是Date類型的,當我們在Jsp頁面中傳遞"1990-08-01"這樣就會報錯的,當然我們可以使用腳本表達式進行屬性的賦值是可以的,比如:
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中引入即可:
1 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
前期工作搞定了,下面就來詳細看一下每個標簽的具體使用方法:
1、c:out標簽的使用:
<c:out> 標簽用於輸出一段文本內容到pageContext對象當前保存的“out”對象中
用法:
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轉義,我們之前都是通過一個方法進行轉義的:
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類型的屬性對象的屬性
用法:
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}"] />
用法:
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域中
用法:
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標簽
這個標簽是控制標簽內容的輸出
用法:
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是一樣的
用法:
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標簽
這個標簽是用來迭代數據的,之前用過這個標簽,但是他還有很多強大的功能:
用法:
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編碼
用法:
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標簽
這個標簽是用來實現重定向的
用法:
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標簽
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:
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:
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:
package com.weijia.choosetag; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; public class OtherWiseTag extends SimpleTagSupport{ @Override public void doTag() throws JspException, IOException { ChooseTag parentTag = (ChooseTag)this.getParent(); if(parentTag.isDo()){ this.getJspBody().invoke(null); } } }
同時需要在tld文件中對這三個標簽類進行描述:
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標簽:
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文件中進行描述一下:
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:
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中核心的方法:
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 }
我們發現他會對每個傳遞進來的對象進行判斷,然后進行一些操作。同時對基本類型進行判斷,但是我們發現這樣的代碼是有點不好看,我們為了體現出我們的技術,我們這里可以將代碼改一下,並且是實現的比他還要好,就是以下的代碼段:
1 2 //使用反射技術可以判斷基本類型的數據類型 3 if(items.getClass().isArray()){ 4 int len = Array.getLength(items); 5 collection = new ArrayList(); 6 for(int i=0;i<len;i++){ 7 collection.add(Array.get(items, i)); 8 } 9 }
這里我們使用反射技術來判斷是不是數組類型,這里可以判斷是基本類型數組還是對象類型數組,然后再使用Array這個工具類進行操作數組中的元素,這樣我們看到這樣的代碼就比jstl中的代碼簡介明了,而且技術上也體現出一點高超。
下面我們在tld文件中進行描述一下:
1 2 <tag> 3 <name>forEach</name> 4 <tag-class>com.weijia.foreach.ForEachTag</tag-class> 5 <body-content>scriptless</body-content> 6 <attribute> 7 <name>var</name> 8 <required>true</required> 9 <rtexprvalue>true</rtexprvalue> 10 </attribute> 11 <attribute> 12 <name>items</name> 13 <required>true</required> 14 <rtexprvalue>true</rtexprvalue> 15 </attribute>
在Jsp頁面中使用:
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文件夾一起打包進去,這時候我們就可以使用這個我們自己定義的標簽庫包了。
引用自:http://blog.csdn.net/jiangwei0910410003/article/details/23915373