這節我們總結一下JSTL自定義標簽相關內容。
1. 自定義標簽簡介
自定義標簽主要用於移除JSP頁面中的Java代碼。Jsp頁面主要是用來顯示給前台的,如果里面有過多的java代碼的話,會顯得很亂,但是沒有java代碼也無法獲取相關數據或完成相關操作。那么這時候我們就可以自己定義一個標簽,來完成需要用java代碼完成的事情,這樣Jsp頁面就會清潔很多,可讀性也更強。JSP中使用自定義標簽移除只需要完成以下兩個步驟:
1)編寫一個實現Tag接口的java類(標簽處理類);
2)編寫標簽庫描述符(tld)文件,在tld文件中對標簽處理類進行描述。
2. 一個簡單實例
我們寫一個簡單的實例來快速理解自定義標簽:輸出客戶機的IP。按照上面描述的兩個步驟,我們首先編寫一個實現Tag接口的java類:
public class ViewIPTag implements Tag { private PageContext pageContext; @Override public int doStartTag() throws JspException { HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); //獲取request JspWriter out = pageContext.getOut(); //獲取out</span> String ip = request.getRemoteAddr(); //通過request獲取客戶機的ip try { out.write(ip); //寫到瀏覽器 } catch (IOException e) { throw new RuntimeException(e); } return 0; } @Override public int doEndTag() throws JspException { return 0; } @Override public Tag getParent() { return null; } @Override public void release() { } @Override public void setPageContext(PageContext arg0) { this.pageContext = arg0; } @Override public void setParent(Tag arg0) { } }
寫好了java類,我們來編寫標簽庫描述符(tld)文件,在WEB-INF目錄下新建一個tld文件,在tld文件中對標簽處理器類進行描述:
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>A tag library exercising SimpleTag handlers.</description> <tlib-version>1.0</tlib-version> <short-name>SimpleTagLibrary</short-name> <uri>/test</uri> <!--為該標簽配一個uri--> <tag> <name>viewIP</name> <!-- 為標簽處理器類配一個標簽名 --> <tag-class>web.tag.ViewIPTag</tag-class> <body-content>empty</body-content> </tag> </taglib>
這樣我們在JSP頁面中就可以導入並使用自定義標簽了:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="/test" prefix="test"%> <!--uri與tld文件中配的uri相同--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>輸出客戶機的IP</title> </head> <body> 您的IP是:<test:viewIP/> <!--該標簽相當於執行下面腳本代碼--> <% String ip = request.getRemoteAddr(); out.write(ip); %> </body> </html>
到這里,應該就能清楚了自定義標簽的定義和配置了。我們來分析一下執行順序:JSP引擎首先通過uri和viewIP標簽名去找tld文件,在tld中通過viewIP找到ViewIPTag類,該類中,首先調用setPageContext方法把頁面的pageContext傳遞進來,再調用setParent把父標簽傳遞進來(沒有則不傳),至此完成了標簽的初始化工作。然后調用doStartTag和doEndTag方法,開始和結束標簽,最后調用release方法釋放標簽,運行時所占的資源。
3. 自定義標簽功能擴展
開發人員在編寫JSP頁面時,經常還需要在頁面中引入一些邏輯,如:
控制JSP頁面某一部分內容是否執行;
控制整個JSP頁面是否執行;
控制JSP頁面內容重復執行;
修改JSP頁面內容輸出。
即:自定義標簽除了可以移除JSP頁面的java代碼外,還可以實現以上功能。下面我們一個個來分析:
1)控制JSP頁面某一部分內容是否執行
新建一個標簽處理類TagDemo1繼承TagSupport類(TagSupport類已經實現了Tag接口,不用直接去實現了),doStartTag方法中可以通過不同的返回值類控制標簽體是否執行。返回Tag.EVAL_BODY_INCLUDE表示執行標簽體內容,返回Tag.SKIP_BODY表示不執行。
//控制標簽體是否執行 public class TagDemo1 extends TagSupport { @Override public int doStartTag() throws JspException { // return Tag.SKIP_BODY;//不執行標簽體內容 return Tag.EVAL_BODY_INCLUDE; //執行標簽體內容 } }
然后在文件中配置好:
<tag> <name>demo1</name> <tag-class>web.tag.TagDemo1</tag-class> <body-content>JSP</body-content> <!--JSP表示標簽體內容為JSP--> </tag>
這樣即可在JSP頁面中可以根據標簽處理類doStartTag方法的返回值控制標簽體內容是否執行。
2)控制整個JSP頁面是否執行
同樣地,doEndTag方法中可以通過不同的返回值類控制下面的JSP頁面是否執行。返回Tag.EVAL_PAGE表示執行下面的JSP頁面內容,返回Tag.SKIP_PAGE表示不執行。如下:
//控制JSP頁面是否執行 public class TagDemo2 extends TagSupport { @Override public int doEndTag() throws JspException { // return Tag.SKIP_PAGE;//不執行JSP頁面 return Tag.EVAL_PAGE;//執行JSP頁面 } }
3)控制JSP頁面內容重復執行
控制頁面內容重復執行的話Tag接口就無法滿足了,需要實現它的子接口IterationTag接口,該接口中有個doAfterBody方法來決定是否重復執行標簽體內容。如果該方法返回IterationTag.EVAL_BODY_AGAIN則繼續執行,如果返回IterationTag.SKIP_BODY則結束重復並跳轉到doEndTag()方法執行了。不過不用直接去實現IterationTag接口,TagSupport類也實現了該接口,所以只要讓標簽處理類繼承TagSupport類即可。需要注意的是除了覆蓋doAfterBody方法外,還得覆蓋doStartTag方法並返回Tag.EVAL_BODY_INCLUDE。因為只有允許標簽體執行才能重復執行。如下:
//控制頁面內容重復執行 public class TagDemo3 extends TagSupport { int i = 5; @Override public int doAfterBody() throws JspException { i--; if(i >= 0) return IterationTag.EVAL_BODY_AGAIN; else return IterationTag.SKIP_BODY; } @Override public int doStartTag() throws JspException { return Tag.EVAL_BODY_INCLUDE; } }
4)修改JSP頁面內容輸出
修改JSP頁面內容輸出的話IterationTag接口就無法滿足了,需要實現它的子接口BodyTag接口,該接口增加了兩個方法:doInitBody()和setBodyContent(BodyContent b)方法。如果doStartTag方法返回BodyTag.EVAL_BODY_BUFFERED,則BodyContent對象就會被JSP翻譯過后的Servlet創建,即得到標簽體對象。在doEndTag方法里對標簽內容進行修改,可以通過this.getBodyContent().getString()拿到轉成String的標簽體內容,然后對該內容進行修改,再將結果result通過this.pageContext().getOut().write(result)進行輸出,在doEndTag方法里返回Tag.EVAL_PAGE。然后JSP翻譯過的servlet再把該BodyContent對象傳遞給setBodyContent方法完成修改。所以我們只需要覆蓋doStartTag和doEndTag方法即可。該接口已經有個默認實現類BodyTagSupport,編寫標簽處理類直接繼承這個類即可。如下:
//修改標簽內容 public class TagDemo4 extends BodyTagSupport { @Override public int doStartTag() throws JspException { return BodyTag.EVAL_BODY_BUFFERED; } @Override public int doEndTag() throws JspException { //拿到標簽體 String content = this.getBodyContent().getString(); String result = content.toUpperCase(); try { this.pageContext.getOut().write(result); } catch (IOException e) { throw new RuntimeException(e); } return Tag.EVAL_PAGE; } }
注:以上擴展功能的知識點在JSP2.0后已經被淘汰掉了,會有一個新的簡單標簽(SimpleTag)接口可以更方便的實現。但是以上的知識點在Struts框架中還在使用。在學Struts框架的標簽庫會遇到。下面我們來看一看簡單標簽的相關內容。
4. 簡單標簽
簡單標簽接口SimpleTag與上面提到的標簽接口不同,SimpleTag標簽提供了一個簡單的doTag()方法代替了doStartTag和doEndTag方法。所有標簽的邏輯、迭代、計算等等都在這個方法中運行。因此SimpleTag接口也可以替代BodyTag接口。該接口中有以下方法:
- void doTag()
- JspTag getParent()
- void setJspBody(JspFragment jspBody):把標簽體通過該方法傳遞進來,我們可以在doTag方法中拿到jspBody對象即拿到了標簽體,然后可以對標簽體做想做的事。包括上面所有功能
- void setJspContext(JspContext pc)
- void setParent(JspTag parent)
JspContext是pageContext的父類,執行順序:JSP引擎首先通過uri和viewIP標簽名去找tld文件,在tld中通過viewIP找到ViewIPTag類,該類中,首先調用setJspContext方法把頁面的pageContext傳遞進來,再調用setParent把父標簽傳遞進來(沒有則不傳),然后再調用setJspBody方法,把代表標簽體的jspFragment對象傳遞進去,至此完成了標簽的初始化工作。然后開始執行標簽,即調用doTag方法。下面我們使用簡單標簽擴展上面的功能。
1)控制JSP頁面某一部分內容是否執行
//控制標簽體是否執行 public class SimpleTagDemo1 extends SimpleTagSupport { //用簡單標簽使用這個方法完成所有業務邏輯 @Override public void doTag() throws JspException, IOException { //得到代表標簽體的JspFragment JspFragment jf = this.getJspBody(); // PageContext pageContext = (PageContext)this.getJspContext(); //獲得pageContext // jf.invoke(pageContext.getOut());//將輸出流放到invoke方法中,寫給瀏覽器 jf.invoke(null);//null默認寫給瀏覽器。不調用該方法即不運行標簽體 } }
在simple.tld文件中配置如下(與上面的基本一樣的):
<?xml version="1.0" encoding="UTF-8" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>A tag library exercising SimpleTag handlers.</description> <tlib-version>1.0</tlib-version> <short-name>SimpleTagLibrary</short-name> <uri>/simpleitcast</uri> <tag> <name>demo1</name> <tag-class>web.simpletag.SimpleTagDemo1</tag-class> <body-content>scriptless</body-content> </tag> </taglib>
2)控制整個JSP頁面是否執行
public class SimpleTagDemo4 extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { throw new SkipPageException();//拋出個SkipPageException異常即不會執行下面的JSP頁面,否則會執行 } }
tld文件中的配置略,跟上面一樣的……不再贅述。
3)控制JSP頁面內容重復執行
//控制標簽體執行10次 public class SimpleTagDemo3 extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); for(int i = 0; i < 10; i++) { jf.invoke(null); } } }
4)修改JSP頁面內容輸出
//將標簽體內容改成大寫 public class SimpleTagDemo3 extends SimpleTagSupport { @Override public void doTag() throws JspException, IOException { JspFragment jf = this.getJspBody(); StringWriter sw = new StringWriter(); jf.invoke(sw);//不要invoke到瀏覽器,先invoke到自己的流里,然后修改修改再輸出 String content = sw.getBuffer().toString();//獲得標簽體的String內容 content = content.toUpperCase(); PageContext pageContext = (PageContext)this.getJspContext(); pageContext.getOut().write(content);//再將流輸出給瀏覽器 } }
5. 帶屬性的標簽
自定義標簽可以定義一個或多個屬性,這樣在JSP頁面中應用自定義標簽時就可以設置這些屬性的值,通過這些屬性為標簽處理器傳遞參數信息,從而提供標簽的靈活性和復用性。想要讓一個自定義標簽具有屬性,通常需要完成兩個任務即可:
1)在標簽處理器中編寫每個屬性對應的setter方法
2)在tld文件中描述標簽的屬性
為自定義標簽定義屬性時,每個屬性都必須按照javaBean的屬性命名方式,在標簽處理器中定義屬性名對應的setter方法,用來接收JSP頁面調用自定義標簽時傳遞進來的屬性值。例如屬性url,在標簽處理器類中就要定義相應的setUrl(String url)方法。在標簽處理器中定義相應的set方法后,JSP引擎在解析執行開始標簽前,也就是調用doStartTag方法前,會調用set屬性方法,為標簽設置屬性。我們看下面的例子:
//通過屬性控制標簽體的執行次數 public class SimpleTagDemo5 extends SimpleTagSupport { public int count; //<itcast:demo5 count="6" public void setCount(int count) {//自動將屬性傳進來 this.count = count; } @Override public void doTag() throws JspException, IOException { for(int i = 0; i < count; i++){ this.getJspBody().invoke(null); } } }
tld文件中配置如下:
<tag> <name>demo5</name> <tag-class>web.simpletag.SimpleTagDemo5</tag-class> <body-content>scriptless</body-content> <attribute> <name>count</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <!-- true的話jsp中該屬性只可以為表達式,false只能為靜態值 --> <type>java.lang.Integer</type> <!-- 指明參數的類型 --> </attribute> </tag>
在JSP頁面中就可以使用標簽
<itcast:demo5 count="4"> xxxx </itcast>
xxxx被輸出4次
6. JSTL案例
我們來用自定義JSTL標簽開發一個防盜鏈的標簽:如果客戶端直接訪問http://localhost:8080/example/test.jsp,會被阻止,先跳轉到主頁index.jsp,再訪問1.jsp
public class RefererTag extends SimpleTagSupport { private String site; private String page; public void setSite(String site) { this.site = site; } public void setPage(String page) { this.page = page; } @Override public void doTag() throws JspException, IOException { //看來訪者是從哪個頁面來的 PageContext pageContext = (PageContext)this.getJspContext(); HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); String referer = request.getHeader("referer");//得到是從哪個頁面來的 //判斷是否從自己的主頁過來的 if(referer == null || !referer.startsWith(site)) { HttpServletResponse response = (HttpServletResponse)pageContext.getResponse(); String webroot = request.getContextPath(); //example if(page.startsWith(webroot)) response.sendRedirect(page); else response.sendRedirect(webroot + page); //重定向后,控制保護的頁面不要執行 throw new SkipPageException(); } } }
看一下index.jsp頁面:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'index.jsp' starting page</title> </head> <body> This is my JSP page. <br> <a href="${pageContext.request.contextPath }/test.jsp">內容</a> <!--掉轉到1.jsp--> </body> </html>
再看一下test.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="/simpleitcast" prefix="it"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <it:referer site="http://localhost:8080/" page="/index.jsp"/> <html> <head> <title>防盜鏈</title> </head> <body> 這是內容 </body> </html>
7. 打包標簽庫
既然我們可以自定義標簽,那我們定義好的標簽如何打包供其他工程使用呢?這在開發中是很重要的。我們按照下列步驟來打包自定義標簽庫:
a. 新建一個普通java project,將原來開發標簽庫工程的src目錄下的標簽處理類全部拷貝過來;
b. 在工程下新建一個lib目錄,把jsp和servlet兩個JAR包(jsp-api.jar和servlet-api.jar)拷貝進來(在tomcat目錄\lib下);
c. 選中這兩個JAR包,右擊->build path->Add to path,變成兩個"奶瓶狀"即可;
d. 在工程下新建一個META-INF目錄,將標簽的配置文件(tld文件)考進來;
e. 將整個工程導出:export->選擇java->JAR File->next->右邊的classpath和project不用打鈎,然后選擇導出目錄即可導出。
這樣標簽庫的JAR包就打包好了。
再新建一個web project,將剛剛打包好的JAR包拷貝到WEB-INF\lib下,這樣在WEB-INF下新建一個JSP文件,在該文件里就可以通過taglib導入剛剛的JAR包了,然后使用自己開發的標簽了。
8. JSTL常用標簽庫
1)<c:out>
<c:out>標簽用於輸出一段文本內容到pageContext對象當前保存的"out"對象中,即:將內容輸出給瀏覽器。該標簽有三個屬性可選,value屬性指定要輸出的內容;escapeXml指定是否將<、>、&、`等特殊字符進行html編碼轉換后再進行輸出,默認為true;default屬性指定如果value屬性的值為null時所輸出的默認值。該標簽主要用於escapeXml屬性和default屬性
2)<c:set>
<c:set>標簽用於把某一個對象存在指定的域范圍內,或者設置web域中的java.util.Map類型的屬性對象或javaBean類型的屬性對象的屬性。有如下屬性:
value:用於指定屬性的值;
scope:用於指定屬性所在的web域;
var:用於指定要設置的web域屬性的名稱;
target:用於指定要設置屬性的對象,這個對象必須是javaBean對象或者java.util.Map對象;
property:用於指定當前為對象設置的屬性名稱。
<!-- c:set標簽 :向web域中存數據,向map或Bean中存數據--> <c:set var="data" value="xxx" scope="page"/> ${pageScope.data } <% Map map = new HashMap(); request.setAttribute("map", map); %> <c:set property="data" value="yyy" target="${map }" /> ${map.data } <c:set property="name" value="eson_15" target="${person }"/> ${person.name }
3) <c:remove>
<c:remove>標簽用於刪除各種web域中的屬性:
<c:remove var="varName" [scope="{page|request|session|application}"]/>
4)<c:if>
<c:if>標簽可以判斷是否執行標簽體
<c:if test="${user==null}" var="result" scope="page"> xxx </c:if>
如果test中的表達式為真則執行標簽體,另外將test的值保存在page域中,保存參數為result,可以通過${result}獲取保存的值。
5)<c:choose>
標簽用於構造條件判斷語句
<c:choose> <c:when test="${count == 0}"> 對不起,沒有符合您要求的記錄 </c:when> <c:otherwise> 符合您要求的記錄共有${count}條 </c:otherwise> </c:choose>
6)<c:forEach>
<c:forEach>標簽用於對一個集合對象中的元素進行循環迭代操作,或者按指定的次數重復迭代執行標簽體中的內容。它有如下屬性:
var屬性:指定當前迭代到的元素保存到page域中的屬性名稱;
items屬性:將要迭代的集合對象;
begin屬性:如果指定items屬性,就從集合中的第begin個元素開始迭代,begin的索引值從0開始編號;如果沒有指定items屬性,就從begin指定的值開始迭代,直到end值結束。
step屬性:指定迭代的步長。
<!-- c:forEach標簽 --> <c:forEach var="num" begin="1" end="10" step="1"> ${num } <!-- 輸出1-10 --> </c:forEach> <br> <% List list = Arrays.asList("1","2"); request.setAttribute("list", list); %> <c:forEach var="index" begin="0" end="${fn.length(list) }"> ${list[index] } </c:forEach> <c:forEach var="index" items="${list}"> ${index} </c:forEach>
7)<c:param>標簽
在JSP頁面進行url的相關操作時,經常用在url地址后面附加一些參數。<c:param>標簽可以嵌套在<c:import>、<c:url>或<c:redirect>標簽內,為這些標簽所使用的url地址附加參數。<c:param>標簽在為一個url地址附加參數時,將自動對參數值進行url編碼,例如,如果傳遞的參數為“中國”,則將其轉換為"%d6%d0%b9%fa"后再附加到url地址后面,這也就是使用<c:param>標簽的最大好處。如:<c:param name="name" value="value"/>
<c:url>標簽用於在JSP頁面中構造一個url地址,其主要目的是實現url重寫。url重寫就是將會話標識號以參數形式附加在url地址后面(關於url重寫,在總結session技術的時候有寫到)。它有如下屬性:
value屬性:指定要構造的url;
var屬性:指定將構造出的url結果保存到web域中的屬性名稱
scope屬性:指定將構造出的url結果保存到那個web域中
<!-- c:url標簽(重點) --> <a href="<c:url value="/servlet/servletDemo1"/>">點點</a> <c:url value="/servlet/servletDemo2" var="servletdemo2"><!-- 如果沒有var,則會把地址直接打給瀏覽器 --> <c:param name="name" value="中國"/> <!-- 中國兩個字已經被url編碼了 --> <c:param name="password" value="我是一個"></c:param> </c:url> <a href="${servletdemo2}">點點</a>
<c:redirect>標簽用於實現請求重定向。
url屬性:指定要轉發或重定向到的目標資源的url地址;
context:當要使用相對路徑重定向到同一個服務器下的其他web應用程序中的資源時,context屬性指定其他web應用程序的名稱。
關於JSTL部分的內容暫時就總結到這吧,如有錯誤之處,歡迎留言指正~