JSP第六篇【自定義標籤之傳統標籤】

爲何要使用自定義標籤?

JSTL標籤庫只提供了簡單的輸出等功能,沒有實現任何的HTML代碼封裝,而且某些複雜類型轉換,或者邏輯處理的時候,JSTL標籤庫完成不了,須要自定義標籤!php

編寫自定義標籤的步驟:

  1. 編寫一個實現Tag接口的Java類【標籤處理器類】
  2. 在WEB-INF目錄下建立tld(Tag Library Descriptor)文件,在tld文件中對標籤處理類(實現Tag接口的Java類)進行描述

快速入門

  • 目標:使用標籤輸出客戶機的IP地址java

  • 按照步驟來:首先編寫一個實現Tag接口的Java類web

public class showIp implements Tag {
	
	    @Override
	    public void setPageContext(PageContext pageContext) {
	
	    }
	
	    @Override
	    public void setParent(Tag tag) {
	
	    }
	
	    @Override
	    public Tag getParent() {
	        return null;
	    }
	
	    @Override
	    public int doStartTag() throws JspException {
	        return 0;
	    }
	
	    @Override
	    public int doEndTag() throws JspException {
	        return 0;
	    }
	
	    @Override
	    public void release() {
	
	    }
	}

複製代碼
  • 既然要獲取到客戶機的IP地址,那麼request對象是必不可少的。如今問題來了,在Tag重寫的方法好像不能直接獲取到request對象啊
  • 通過我一番仔細的觀察,發現了下面這個方法:
@Override
	    public void setPageContext(PageContext pageContext) {
	
	    }

複製代碼
  • 既然能獲取到pageContext對象,那麼其餘8大內置對象還不是隨隨便便?因而乎,我就定義一個成員變量pageContext,在setPageContext()方法中傳遞過來的pageContext賦值給我定義的成員變量便可
private PageContext pageContext = null;

	    @Override
	    public void setPageContext(PageContext pageContext) {
	        this.pageContext = pageContext;
	    }

複製代碼
  • 好的,看回咱們的需求:使用標籤輸出客戶機的IP地址。在上面剩餘5個方法中,最有可能就是在doStartTag()方法中編寫代碼
@Override
    public int doStartTag() throws JspException {

        //獲取到request對象
        HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();

        //獲取到客戶機的ip地址
        String ip = httpServletRequest.getRemoteAddr();
        
        //獲取輸出到瀏覽器的對象
        JspWriter jspWriter = pageContext.getOut();
        
        //下面的異常只能捕獲,由於子類的異常不能比父類多
        try {
            jspWriter.write(ip);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return 0;
    }

複製代碼
  • 接着,編寫tld文件,描述實現Tag接口的Java類【標籤處理類】
<?xml version="1.0" encoding="ISO-8859-1"?>
	
	<taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1">
	
	    <tlib-version>1.0</tlib-version>
	    <short-name>zhongfucheng</short-name>
	    <uri>/zhongfucheng</uri>
	
	    <!-- Invoke 'Generate' action to add tags or functions -->
	    <tag>
	        <name>viewIp</name>
	        <tag-class>tag.showIp</tag-class>
	        <body-content>empty</body-content>
	    </tag>
	
	
	</taglib>

複製代碼
  • 下面咱們來測試一下看能不能用


標籤處理類詳細說明

看完上面的程序,大部分人都是懵逼的。由於還不知道它具體是怎麼用的,調用順序是什麼瀏覽器

  • 首先咱們來看一下Tag接口的源碼
public interface Tag extends JspTag {
	    int SKIP_BODY = 0;
	    int EVAL_BODY_INCLUDE = 1;
	    int SKIP_PAGE = 5;
	    int EVAL_PAGE = 6;
	
	    void setPageContext(PageContext var1);
	
	    void setParent(Tag var1);
	
	    Tag getParent();
	
	    int doStartTag() throws JspException;
	
	    int doEndTag() throws JspException;
	
	    void release();
	}


複製代碼
  • 上面程序的執行流程:
    • JSP引擎遇到自定義標籤,首先建立標籤處理器類的實例對象
    • JSP引擎實例化完標籤處理器類後,調用setPageContext()方法,將pageContext對象傳遞給標籤處理器類,使得標籤處理器類能夠經過pageContext對象與JSP頁面進行通訊!
    • setPageContext()方法執行完後,調用setParent()方法,將當前標籤的父標籤傳遞給當前處理器類,若是當前標籤沒有父標籤,則傳入null
    • 當WEB容器執行到自定義標籤的開始標記時,調用doStartTag()方法。
    • 當WEB容器執行到自定義標籤的結束標記時,調用doEndTag()方法。
    • 通常來講,當WEB容器執行完自定義標籤後,標籤處理器類會駐留在內存中,直至中止WEB應用時,WEB容器纔會調用release()方法

這裏寫圖片描述


  • 咱們如今已經清楚了方法的執行順序了,可Tag接口的源碼還有4個變量阿,它們是用來作什麼的呢?咱們在編寫JSP頁面時,常常須要在頁面中引入一些邏輯,例如:緩存

    • 控制JSP頁面某一部分(標籤體)是否執行
    • 控制整個JSP頁面是否執行
    • 控制JSP頁面內容重複執行
    • 修改JSP頁面內容輸出
  • 再看回4個變量的名字,咱們能夠發現,這4個變量就是用來作邏輯判斷的微信

  • 咱們來測試一下吧,在doEndTag()方法中,返回的是SKIP_PAGE變量,看下會怎麼樣less

@Override
    public int doEndTag() throws JspException {
        return SKIP_PAGE;
    }


複製代碼
  • 咱們再來看一看效果:

  • 好像是沒什麼區別!咱們再查看一下源代碼,發現執行完標籤後,後面的代碼全都沒有執行!

  • doStartTag()方法使用的是SKIP_BODY和EVAL_BODY_INCLUDE這兩個變量,判斷是否執行標籤體的內容。
  • doEndTag()方法使用的是SKIP_PAGE和EVAL_PAGE這兩個變量,判斷是否執行剩下頁面的內容
  • 控制JSP頁面內容重複執行和修改JSP頁面內容輸出後面會有!

tld文件詳細說明

這裏寫圖片描述

這裏寫圖片描述

  • 首先咱們來看一下tld文件當前用到的內容吧
<tlib-version>1.0</tlib-version>
    <short-name>myshortname</short-name>
    <uri>http://mycompany.com</uri>
    
    <tag>
        <name></name>
        <tag-class></tag-class>
        <body-content></body-content>
    </tag>

複製代碼
  • 咱們一個一個來看:
    • shortname推薦使用prefix
    • uri就是引入這個標籤庫使用的uri
    • name爲標籤名
    • tagclass爲實現類
    • bodycontent爲標籤體的限制,它有4個值: EMPTY【不容許有標籤體】,JSP【容許有JSP代碼】 ,scriptless【不容許有腳本代碼(也就是<%%>),容許有EL表達式,文本,JSP行爲】 , tagdepentend【標籤體內的JSP代碼不會被解析,直接輸出文本】

TagSupport類

大部分時候咱們都不須要實現Tag接口來編寫自定義標籤,TagSupport是Tag的一個模板類,實現了pageContext,parent的getter、setter方法以及一些其餘的功能。咱們要作的就是重寫doStartTag()和doEndTag()方法jsp

  • 下面咱們就來簡單使用一下吧:ide

  • 繼承TagSupport類,重寫doStartTag()方法,比直接實現Tag接口簡潔不少!學習

public class Demo1 extends TagSupport {
	
	    @Override
	    public int doStartTag() throws JspException {
	
	        //獲取到request對象
	        HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();
	
	        String method = httpServletRequest.getMethod();
	
	        JspWriter jspWriter = pageContext.getOut();
	
	        try {
	            jspWriter.write(method);
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        return 0;
	    }
	}

複製代碼
  • 在tld文件中描述一把
<tag>
        <name>showMethod</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>empty</body-content>
    </tag>

複製代碼
  • 效果:

帶屬性的標籤

上面咱們編寫的自定義標籤都沒有附帶屬性的,咱們在使用core標籤庫的時候,標籤通常都帶有屬性

其實JSTL標籤庫的原理就是自定義標籤,把自定義標籤搞明白了,對JSTL標籤庫的使用就有更好的理解了

  • 想要自定義標籤帶有屬性也很是簡單,只要在標籤處理器類上加一個成員變量和setter、getter(),再在tld文件中描述下該屬性便可!它的原理是這樣的:當標籤使用到屬性的時候,引擎就會調用它的setter()方法

  • 下面我想要完成的功能是:使用標籤的人,傳入一個字符串格式就能夠顯示想要的格式日期

  • 編寫標籤處理器類,增長一個成員變量以及對應的setter、getter方法

public class Demo1 extends TagSupport {
	
	
	    //建立成員對象,對應的setter、getter方法
	    private String format = null;
	
	
	    @Override
	    public int doStartTag() throws JspException {
	
	        //建立日期格式化對象
	        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
	
	        //格式化日期並向瀏覽器輸出
	        try {
	            pageContext.getOut().write(simpleDateFormat.format(new Date()));
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        return 0;
	    }
	
	    public String getFormat() {
	        return format;
	    }
	
	    public void setFormat(String format) {
	        this.format = format;
	    }
	}

複製代碼
  • 在tld文件中描述標籤和屬性,name表明的是屬性的名字,required表明的是是否爲必須,rtexprvalue表明可否使用EL表達式
<tag>
        <name>formatDate</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>format</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>

複製代碼
  • 咱們來看一下效果:


標籤的繼承關係

  • 在深刻講解以前,咱們先來看一下各類Tag接口、類之間的關係,這樣學習下去纔不會暈

IterationTag說明

  • 咱們已經使用過了Tag接口和TagSupport類了。接下來咱們看一下IterationTag是什麼玩意。
public interface IterationTag extends Tag {
	    int EVAL_BODY_AGAIN = 2;
	
	    int doAfterBody() throws JspException;
	}

複製代碼
  • 從關係圖咱們也能夠看出,IterationTag接口實現了Tag接口,InterationTag接口和Tag接口最主要的區別就是多了個doAfterBody()方法和EVAL_BODY_AGAIN變量
  • 理解起來也很簡單:當doAfterBody()返回的是EVAL_BODY_AGAIN變量,那麼標籤體的內容就一直循環!固然了,TagSupport也實現了Iteration接口,也就是說TagSupport類也能完成Iteration接口的事情
  • 咱們來使用一下吧:
public class Demo1 extends TagSupport {
	
	    @Override
	    public int doStartTag() throws JspException {
	
	        try {
	            pageContext.getOut().write("hello");
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        //執行標籤體
	        return EVAL_BODY_INCLUDE;
	    }
	
	    @Override
	    public int doAfterBody() throws JspException {
	
	        //標籤體不斷循環,直到doAfterBody()返回的是SKIP_BODY
	        return EVAL_BODY_AGAIN;
	    
	    }
	}

複製代碼
  • tld文件中描述,既然標籤體有內容,就不能用empty了
<tag>
        <name>foreverEval</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>tagdependent</body-content>
    </tag>

複製代碼
  • 注意看橫向的滑輪,已經死循環輸出了:

  • doAfterBody()中只要返回的是SKPI_BODY就退出循環,執行doEndTag()方法
//定義一個變量,規定標籤體循環的次數
	    int x = 0;
	    
	    @Override
	    public int doStartTag() throws JspException {
	
	        try {
	            pageContext.getOut().write("hello");
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        //執行標籤體
	        return EVAL_BODY_INCLUDE;
	    }
	
	    @Override
	    public int doAfterBody() throws JspException {
	        
	        x++;
	        if (x >= 10) {
	            return SKIP_BODY;
	        }
	
	        //標籤體不斷循環,直到doAfterBody()返回的是SKIP_BODY
	        return EVAL_BODY_AGAIN;
	
	    }

複製代碼
  • 如今咱們已經能控制循環的次數了


BodyTag說明

前面咱們已經使用到了帶標籤體的自定義標籤了,前面的都是隻能直接輸出而得不到標籤體的內容,既然得不到標籤體的內容,就更別說修改標籤體了

  • 此時,咱們就須要BodyTag接口的支持了!它專門用來處理帶標籤體的標籤,下面咱們來看一下BodyTag的源碼
public interface BodyTag extends IterationTag {
	    /** @deprecated */
	    int EVAL_BODY_TAG = 2;

	    int EVAL_BODY_BUFFERED = 2;
	
	    void setBodyContent(BodyContent var1);
	
	    void doInitBody() throws JspException;
	}

複製代碼
  • BodyTag多了EVAL_BODY_BUFFERED變量【一個已經標識過期了】,多了setBodyContent和doInitBody()兩個方法

  • 其實使用BodyTag十分簡單

    • 若是doStartTag()方法返回的是EVAL_BODY_BUFFERED,把標籤體的內容緩存起來
    • 接着調用setBodyContent()方法和doInitBody()方法,封裝標籤體的內容到BodyContent對象中
    • 接着調用doEndTag()方法
    • 對於標籤體的內容,咱們能夠經過getBodyContenet()來獲取!
  • 再看回上面的關係圖,BodyTag實現了IterationTag和Tag接口,若是直接實現BodyTag接口作開發,要實現的方法就太多了。通常咱們使用繼承BodyTag的BodyTagSupport來作開發


BodyTagSupport說明

  • 首先來看一下源代碼吧:
public class BodyTagSupport extends TagSupport implements BodyTag {
	    protected BodyContent bodyContent;
	
	    public BodyTagSupport() {
	    }
	
	    public int doStartTag() throws JspException {
	        return 2;
	    }
	
	    public int doEndTag() throws JspException {
	        return super.doEndTag();
	    }
	
	    public void setBodyContent(BodyContent b) {
	        this.bodyContent = b;
	    }
	
	    public void doInitBody() throws JspException {
	    }
	
	    public int doAfterBody() throws JspException {
	        return 0;
	    }
	
	    public void release() {
	        this.bodyContent = null;
	        super.release();
	    }
	
	    public BodyContent getBodyContent() {
	        return this.bodyContent;
	    }
	
	    public JspWriter getPreviousOut() {
	        return this.bodyContent.getEnclosingWriter();
	    }
	}

複製代碼
  • 能夠發現:BodyTagSupport主要擴充瞭如下的內容:
    • 把BodyContent直接定義爲成員變量,在獲取標籤體內容的時候就不須要經過getBodyContent()獲取了
    • 提供獲取JspWriter的方法,不須要從pageConext中獲取了
    • 以上的兩個擴充都簡化了咱們的代碼書寫
protected BodyContent bodyContent;

	    public JspWriter getPreviousOut() {
	        return this.bodyContent.getEnclosingWriter();
	    }


複製代碼
  • 從BodyTag接口中,我就說到了:標籤體的內容封裝到了BodyContent類中,那麼BodyContent類到底是什麼?咱們來看一下源碼
public abstract class BodyContent extends JspWriter {
	    private JspWriter enclosingWriter;
	
	    protected BodyContent(JspWriter e) {
	        super(-2, false);
	        this.enclosingWriter = e;
	    }
	
	    public void flush() throws IOException {
	        throw new IOException("Illegal to flush within a custom tag");
	    }
	
	    public void clearBody() {
	        try {
	            this.clear();
	        } catch (IOException var2) {
	            throw new Error("internal error!;");
	        }
	    }
	
	    public abstract Reader getReader();
	
	    public abstract String getString();
	
	    public abstract void writeOut(Writer var1) throws IOException;
	
	    public JspWriter getEnclosingWriter() {
	        return this.enclosingWriter;
	    }
	}


複製代碼
  • 原來BodyContent繼承着JspWriter,它與JspWriter最大的區別是:BodyContent類的任何寫入的內容並不自動地向頁面輸出!

  • 咱們通常使用BodyContent都使用兩個方法:

//將數據轉變成Reader對象
    public abstract Reader getReader();

	//將數據轉變成String對象
    public abstract String getString();

複製代碼
  • 再從關係圖咱們能夠看初,BodyTagSupport繼承了TagSupport類實現了BodyTag接口,能夠說:BodyTagSupport有着前面講的接口和類的全部功能!

  • 下面咱們來使用下BodyTagSupport將標籤體的內容轉成是小寫的

  • 標籤處理器類

public class Demo1 extends BodyTagSupport {
	
	    @Override
	    public int doStartTag() throws JspException {
	
	
	        //想要獲取到標籤體的內容,就要返回EVAL_BODY_BUFFERED變量
	
	        return EVAL_BODY_BUFFERED;
	
	    }
	
	    @Override
	    public int doEndTag() throws JspException {
	
	        //獲取到標籤體的內容
	        String value = bodyContent.getString();
	
	        //將標籤體的內容轉成小寫並輸出
	        try {
	            this.getPreviousOut().write(value.toLowerCase());
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        return super.doEndTag();
	    }
	
	}


複製代碼
  • tld文件:
<tag>
        <name>BodyContentToLowerCase</name>
        <tag-class>tag.Demo1</tag-class>
        <body-content>tagdependent</body-content>
    </tag>

複製代碼
  • 效果:


若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y.

相關文章
相關標籤/搜索