JSTL自定義標簽


    這節我們總結一下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接口。該接口中有以下方法:

  1. void doTag()  
  2. JspTag getParent()  
  3. void setJspBody(JspFragment jspBody):把標簽體通過該方法傳遞進來,我們可以在doTag方法中拿到jspBody對象即拿到了標簽體,然后可以對標簽體做想做的事。包括上面所有功能  
  4. void setJspContext(JspContext pc)  
  5. 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部分的內容暫時就總結到這吧,如有錯誤之處,歡迎留言指正~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM