jstl csdn導入 JavaWeb學習篇之----自定義標籤&&JSTL標籤庫詳解

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

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


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

首先咱們自定義標籤類:ViewIpTag數據庫

package com.weijia.traditionaltag;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * 自定義標籤,而後將這個標籤映射到這個類:mytag:viewIP
 * 記得將自定義的標籤綁定到一個url上面,這個url通常是公司的網址
 * 
 */
public class ViewIpTag extends TagSupport{

	private static final long serialVersionUID = 1L;

	@Override
	public int doStartTag() throws JspException {
		//內置一個pageContext對象,咱們以前說到pageContext對象,它裏面是封裝了9個隱式對象
		HttpServletRequest request = (HttpServletRequest)this.pageContext.getRequest();
		JspWriter out = this.pageContext.getOut();
		String ip = request.getRemoteAddr();
		try {
			out.print(ip);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return super.doStartTag();
	}

}

自定義tld文件,mytag.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 web-jsptaglibrary_2_0.xsd"
    version="2.0">
    
  <description>JSTL 1.1 core library</description>
  <display-name>JSTL core</display-name>
  <tlib-version>1.1</tlib-version>
  <short-name>weijia</short-name>
  <uri>http://www.weijia.cn/mytag</uri>

  <!-- 顯示IP地址 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>viewIP</name>
    <tag-class>com.weijia.traditionaltag.ViewIpTag</tag-class>
    <body-content>empty</body-content>
  </tag>
</taglib>
這裏咱們將就自定義的標籤類就註冊好了,下面解釋一下這些字段的含義:

首先看一下:apache

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

<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規範定義的通訊規則依次調用它的方法。
一、public void setPageContext(PageContext pc), JSP引擎實例化標籤處理器後,將調用setPageContext方法將JSP頁面的pageContext對象傳遞給標籤處理器,標籤處理器之後能夠經過這個pageContext對象與JSP頁面進行通訊。
二、public void setParent(Tag t),setPageContext方法執行完後,WEB容器接着調用的setParent方法將當前標籤的父標籤傳遞給當前標籤處理器,若是當前標籤沒有父標籤,則傳遞給setParent方法的參數值爲null。
三、public int doStartTag(),調用了setPageContext方法和setParent方法以後,WEB容器執行到自定義標籤的開始標記時,就會調用標籤處理器的doStartTag方法。
四、public int doEndTag(),WEB容器執行完自定義標籤的標籤體後,就會接着去執行自定義標籤的結束標記,此時,WEB容器會去調用標籤處理器的doEndTag方法。
五、public void release(),一般WEB容器執行完自定義標籤後,標籤處理器會駐留在內存中,爲其它請求服務器,直至中止web應用時,web容器纔會調用release方法。

我能夠查看咱們上面的例子翻譯後的Jsp代碼:

out.write("<body>  \r\n");
 out.write("\t<!-- 顯示客戶機的IP地址 -->\r\n");
 out.write("\t客戶機的IP地址是:");
 if (_jspx_meth_mytag_005fviewIP_005f0(_jspx_page_context))
     return;
 out.write("\r\n");
 out.write("\t\r\n");


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

private boolean _jspx_meth_mytag_005fviewIP_005f0(PageContext _jspx_page_context)
          throws Throwable {
    PageContext pageContext = _jspx_page_context;
    JspWriter out = _jspx_page_context.getOut();
    //  mytag:viewIP
    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);
    _jspx_th_mytag_005fviewIP_005f0.setPageContext(_jspx_page_context);
    _jspx_th_mytag_005fviewIP_005f0.setParent(null);
    int _jspx_eval_mytag_005fviewIP_005f0 = _jspx_th_mytag_005fviewIP_005f0.doStartTag();
    if (_jspx_th_mytag_005fviewIP_005f0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE) {
      _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);
      return true;
    }
    _005fjspx_005ftagPool_005fmytag_005fviewIP_005fnobody.reuse(_jspx_th_mytag_005fviewIP_005f0);
    return false;
  }
咱們能夠看到,首先這個方法接收的是一個pageContext變量對象,這個和咱們以前說的同樣,自定義標籤類中有一個pageContext變量對象就能夠操做其餘對象了,下面來看一下那個方法的代碼,首先他會去加載那個標籤類,同時注意到首先是執行setPageContext()方法的,將pageContext變量傳遞到標籤類中,而後看setParent()方法傳遞的是null,由於咱們打印IP的標籤沒有父標籤的,接下來執行doStartTag()方法,而後再執行doEndTag()方法,這裏咱們看到是作個判斷,若是doEndTag方法返回的值是Tag.SKIP_PAGE的話,就是說餘下的jsp頁面不執行了,因此返回一個true,那麼咱們看到上面的if判斷代碼中,若是這個方法返回true的話,直接return,下面的代碼就不執行了。大致流程就是這樣的。


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

一、不執行標籤體內容

自定義標籤類:

package com.weijia.traditionaltag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * 是否輸出標籤體內容
 * @author weijiang204321
 *
 */
public class TagDemo1 extends TagSupport{

	@Override
	public int doStartTag() throws JspException {
		return TagSupport.EVAL_BODY_INCLUDE;//輸出標籤體內容
		//return TagSupport.SKIP_BODY;//不輸出標籤體內容
	}

}

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


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

<!-- 是否顯示標籤體 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo1</name>
    <tag-class>com.weijia.traditionaltag.TagDemo1</tag-class>
    <body-content>JSP</body-content>
  </tag>

由於是有標籤體內容的,因此<body-content>標籤的值是JSP


在Jsp頁面中使用:

<!-- 不執行標籤體 -->
<simpletag:demo1>
	aaaa
</simpletag:demo1>


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

自定義標籤類:

package com.weijia.traditionaltag;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * 控制整個JSP是否輸出
 * @author weijiang204321
 *
 */
public class TagDemo2 extends TagSupport{

	@Override
	public int doStartTag() throws JspException {
		return super.doStartTag();
	}

	@Override
	public int doEndTag() throws JspException {
		return TagSupport.EVAL_PAGE;
		//return TagSupport.SKIP_PAGE;不執行餘下的jsp內容
	}

}
當doEndTag方法返回的是TagSupport.EVAL_PAGE常量的話就執行jsp餘下的內容,若是返回的是TagSupport.SKIP_PAGE常量的話就不執行jsp餘下的內容


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

<!-- 控制是否顯示jsp頁面 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo2</name>
    <tag-class>com.weijia.traditionaltag.TagDemo2</tag-class>
    <body-content>empty</body-content>
  </tag>

在JSP頁面中使用:

<!-- 不執行餘下的頁面內容 -->
<simpletag:demo2/>
這樣使用以後,在這個標籤以後的內容就不會執行了,咱們在上面分析源代碼的時候已經解析過了。頁面都不會含有餘下的內容了,若是咱們將這個標籤放在頁面的第一行,那麼這個頁面就是一片空白,咱們在瀏覽器中查看頁面的源代碼,也是發現一片空白的,由於out對象沒有進行print了


三、重複執行標籤體內容

自定義標籤體類:

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常量,由於咱們要執行標籤體內容的。


註冊自定義標籤類:

<!-- 控制標籤體重複輸出 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo3</name>
    <tag-class>com.weijia.traditionaltag.TagDemo3</tag-class>
    <body-content>JSP</body-content>
  </tag>

在Jsp頁面中使用:

<!-- 重複執行標籤體內容 -->
<simpletag:demo3>
	aaaa
</simpletag:demo3>
這時候在頁面中就會輸出5個aaaa


四、修改標籤體內容

自定義標籤類:

package com.weijia.traditionaltag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;

/**
 * 修改標籤體內容
 * @author weijiang204321
 *
 */
public class TagDemo4 extends BodyTagSupport{

	@Override
	public int doEndTag() throws JspException {
		BodyContent bc = this.getBodyContent();//獲取標籤體內容對象
		String content = bc.getString();
		content = content.toUpperCase();//將標籤體內容轉成大寫
		try {
			this.pageContext.getOut().write(content);//在將轉化以後的內容輸出到瀏覽器中
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return BodyTagSupport.EVAL_BODY_INCLUDE;
	}

	@Override
	public int doStartTag() throws JspException {
		return BodyTagSupport.EVAL_BODY_BUFFERED;//這裏返回緩存標籤體內容常量
	}

}

這裏咱們要注意的是,咱們繼承的是BodyTagSupport類了,要在doStartTag方法中返回BodyTagSupport.EVAL_BODY_BUFFERED常量,才能夠取出標籤體內容緩存,而後再doEndTag方法中取出標籤體內容而後進行操做以後再寫到瀏覽器中。


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

<!-- 修改標籤體內容 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo4</name>
    <tag-class>com.weijia.traditionaltag.TagDemo4</tag-class>
    <body-content>JSP</body-content>
  </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),讓標籤體的執行結果輸出到該輸出流對象中,而後從該輸出流對象中取出數據進行修改後再輸出到目標設備,便可達到修改標籤體的目的。


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

一、是否輸出標籤體內容

自定義標籤類:

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的話也是向瀏覽器中輸出標籤體內容的


註冊標籤體類:

<!-- 是否顯示標籤體 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo1</name>
    <tag-class>com.weijia.simpletag.SimpleTagDemo1</tag-class>
    <body-content>scriptless</body-content>
  </tag>
這裏的註冊和傳統標籤不同的就是<body-content>標籤的值是scriptless而不是JSP了


在JSP頁面使用:

<simpletag:demo1>
	aaa
</simpletag:demo1>


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

自定義標籤類:

package com.weijia.sampletag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

/**
 * 控制不執行餘下的jsp內容
 * @author weijiang204321
 *
 */
public class SimpleTagDemo2 extends SimpleTagSupport{

	@Override
	public void doTag() throws JspException, IOException {
		//直接拋出異常就不會執行餘下的jsp內容
		throw new SkipPageException();
	}

}
咱們只要在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頁面內容就不會輸出了


三、標籤體重複執行

自定義標籤類:

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頁面中使用:
<simpletag:demo3>
	aaaa
</simpletag:demo3>

四、修改標籤體內容

package com.weijia.sampletag;

import java.io.IOException;
import java.io.StringWriter;

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();
		StringWriter sw = new StringWriter();
		jf.invoke(sw);
		String content = sw.toString();
		content = content.toUpperCase();
		this.getJspContext().getOut().write(content);
	}

}
咱們將StringWriter對象傳遞到invoke方法中,而後再經過StringWriter對象獲得標籤體內容,進行操做,而後再經過out對象輸出到瀏覽器中。


註冊標籤類:

<!-- 修改標籤體內容 -->
  <tag>
    <description>
        Catches any Throwable that occurs in its body and optionally
        exposes it.
    </description>
    <name>demo4</name>
    <tag-class>com.weijia.simpletag.SimpleTagDemo4</tag-class>
    <body-content>scriptless</body-content>
  </tag>

在Jsp頁面中使用:

<!-- 修改標籤體內容 -->
<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>


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



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

自定義標籤類:

package com.weijia.propertytag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class PropertyTag extends SimpleTagSupport{

	private int count = 0;
	
	public void setCount(int count){
		this.count = count;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		JspFragment jf = this.getJspBody();
		for(int i=0;i<count;i++){
			jf.invoke(null);
		}
	}

}
這裏須要定義一個變量來記錄執行的次數,同時還須要提供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頁面中使用:

<propertytag:demo count="9">
	aaaaa
</propertytag:demo>
在頁面中輸出9次aaaaa


雖然咱們這裏看到了輸出的很簡單,設置也很簡單,可是這裏面仍是有不少內容的

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

<propertytag:demo count="<%=new Date()%>">
	aaaaa
</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中引入便可:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
前期工做搞定了,下面就來詳細看一下每一個標籤的具體使用方法:

一、c:out標籤的使用:

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


用法:

<!-- c:out標籤 -->
    <!-- 輸出給瀏覽器,可是不必這樣輸出是沒意義的,能夠直接輸出的aaaaaa -->
    <c:out value="aaaa"></c:out>
    <!-- 轉義以後輸出/默認值,這樣c:out標籤纔有意義-->
    <c:out value="<a href=''>點點</a>" default="aaa" escapeXml="true"></c:out>
    <%
    	request.setAttribute("data","xxx");
     %>
     <c:out value="${data}" default="aaa"></c:out>
這個標籤很簡單的,就是就是向瀏覽器中直接輸出內容的,那麼這個和使用EL表達式輸出有什麼區別呢,他有什麼特別的好處呢?

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

private String htmlFilter(String message){
		if(message == null){
			return null;
		}
		
		char[] content = new char[message.length()];
		message.getChars(0, message.length(), content, 0);
		StringBuffer result = new StringBuffer(message.length()+50);
		for(int i=0;i<content.length;i++){
			switch(content[i]){
			case '<':
				result.append("<");
				break;
			case '>':
				result.append(">");
				break;
			case '&':
				result.append("&");
				break;
			case '"':
				result.append(""");
				break;
			default:
				result.append(content[i]);
			}
		}
		return result.toString();
	}
只要設置這個屬性值爲true的話,咱們就不須要手動的進行轉義了,因此說這個標籤仍是有他特定的功能的,可不能忘記他呀!


二、c:set標籤

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


用法:

<!-- c:set標籤 -->
  	<!-- 向page域中存入到xxx -->
  	<c:set var="data" value="xxx" scope="page"/>
  	${data}
  	<!-- 向map中存入數據 -->
  	<%
  		Map map = new HashMap();
  		request.setAttribute("map",map);
  	 %>
  	 <c:set property="name" value="uuu" target="${map}"/>
  	 ${map.name}
  	 <!-- 向javabean中存入數據 -->
  	 <%
  	 	Person p = new Person();
  	 	request.setAttribute("p",p);
  	  %>
  	 <c:set property="name" value="uuu" target="${p}"/>
  	 ${p.name}
這個標籤能夠指定在四個域中設置屬性值,同時設置的對象不只只有基本類型,還能夠設置對象類型,集合類型


三、c:remove標籤

這個標籤能夠在四個域中刪除指定的屬性

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

用法:

<!-- c:remove標籤 -->
  <!-- 刪除屬性 -->
  <%
    request.setAttribute("data","xxx");
  %>
  <c:remove var="data" scope="request"/>

四、c:catch標籤

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

其語法格式以下:

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

用法:

<!-- c:catch異常捕獲 -->
  	 <!--var是存入異常對象的關鍵字 -->
  	 <c:catch var="myex">
  	 <%
  		int x = 1/0;
  	 %>
  	 </c:catch>
  	 <!-- 異常對象必需要有message屬性 -->
  	 ${myex.message}

五、c:if標籤

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


用法:

<!-- c:if -->
  	<!-- 將判斷結果以aaa爲關鍵字存入到域中 -->
  	<%
  		request.setAttribute("user",null);
  	 %>
  	 ${user}
  	<c:if var="aaa" test="${user == null}",scope="page"/>
  	 ${aaa}
同時能夠將判斷條件的值使用變量存起來


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

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

用法:

<!-- c:choose標籤 -->
  <c:choose>
  	<c:when test="${true}">
  		aaaa
  	</c:when>
  	<c:otherwise>
  		bbb
  	</c:otherwise>
  </c:choose>

七、c:forEach標籤

這個標籤是用來迭代數據的,以前用過這個標籤,可是他還有不少強大的功能:


用法:

<!-- c:forEach -->
  	<%
  		List list = new ArrayList();
  		list.add("aaa");
  		list.add("bbb");
  		list.add("ccc");
  		list.add("ddd");
  		request.setAttribute("list",list);
  	 %>
 	
  	<c:forEach var="str" items="${list}">
  		${str}
  	</c:forEach>
  	
  	<!-- 設置步長,分頁功能 -->
  	<c:forEach  var="num" items="${list}" begin="1" end="9" step="1">
  		${num}
  	</c:forEach>
  	
  	<!-- 記錄迭代變量的值 -->
  	<c:forEach var="str" items="${list}" varStatus="status">
  		${status.count}
  	</c:forEach>

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

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

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


這個屬性的功能咱們能夠實現表格中的奇數和偶數行的不一樣顯示


八、c:param標籤

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


九、c:url標籤

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


用法:

<!-- c:url標籤 -->
  	<!-- url重寫 -->
  	<c:url var="url" value="JspDemo/1.jsp"/>
  		<a href='${url}'>購買</a>
  	</c:url>
  	<!-- url標籤直接輸出url -->
  	<a href='<c:url value="/1.jsp"'>點點</a>
  	<!-- c:url構建參數(自動url編碼) -->
  	<c:url var="index" value="/1.jsp">
  		<c:param name="name" value="中國"/>
  	</c:url>

10.c:redirect標籤

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


用法:

<c:redirect url="JspDemo/1.jsp">
  	<c:param name="name" value="jiangwei"></c:param>
</c:redirect>
咱們在以前介紹的jsp標籤中只有<jsp:forword>轉發標籤,而沒有重定向的標籤,那麼這個就有重定向的標籤了。

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


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

一、開發if標籤

package com.weijia.iftag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class IfTag extends SimpleTagSupport{

	private boolean test;
	
	public void setTest(boolean test){
		this.test = test;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		if(test){
			this.getJspBody().invoke(null);
		}
	}

}
咱們定義一個boolean類型的test變量來保存判斷值,而後經過這個判斷值來控制是否輸出標籤體內容


二、開發choose/when/otherwise標籤,在開發這套標籤的時候,咱們會發現遇到一個難處就是多個標籤之間須要進行通訊,好比說一個標籤執行了標籤體內容,那麼其餘標籤體的內容就不能執行了,因此這裏須要給多個標籤體外面在套一個父標籤(這也是一個父標籤開發的案例),經過那麼每一個子標籤能夠經過父標籤中的一個變量來判斷是否執行本身的標籤體,原理就是這樣的,下面是實現類,其中ChooseTag是父標籤了,WhenTag和OtherWiseTag是子標籤,同時給WhenTag標籤訂義一個boolean屬性來接收外界的判斷條件

ChooseTag:

package com.weijia.choosetag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class ChooseTag extends SimpleTagSupport{
	
	private boolean isDo;

	public boolean isDo() {
		return isDo;
	}

	public void setDo(boolean isDo) {
		this.isDo = isDo;
	}

	@Override
	public void doTag() throws JspException, IOException {
		this.getJspBody().invoke(null);
	}

}

WhenTag:

package com.weijia.choosetag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class WhenTag extends SimpleTagSupport {

	private boolean test;
	
	public void setTest(boolean test){
		this.test = test;
	}
	
	@Override
	public void doTag() throws JspException, IOException {
		//獲取到父標籤
		ChooseTag parentTag = (ChooseTag)this.getParent();
		if(test && !parentTag.isDo()){
			this.getJspBody().invoke(null);
			parentTag.setDo(true);
		}
	}

}

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文件中對這三個標籤類進行描述:

<tag>
    <name>choose</name>
    <tag-class>com.weijia.choosetag.ChooseTag</tag-class>
    <body-content>scriptless</body-content>
  </tag>
  
  <tag>
    <name>when</name>
    <tag-class>com.weijia.choosetag.WhenTag</tag-class>
    <body-content>scriptless</body-content>
    <attribute>
        <name>test</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>
  
  <tag>
    <name>otherwise</name>
    <tag-class>com.weijia.choosetag.OtherWiseTag</tag-class>
    <body-content>scriptless</body-content>
  </tag>


三、for:Each標籤:

package com.weijia.foreach;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import sun.text.CompactShortArray.Iterator;

/**
 * ForEach標籤
 * @author weijiang204321
 *
 */
public class ForEachTag extends SimpleTagSupport{

	private Object items;
	private String var;
	private Collection collection;
	
	public void setItems(Object items) {
		this.items = items;
		if(items instanceof Collection){
			collection = (Collection)items;
		}
		
		if(items instanceof Map){
			Map map = (Map) items;
			collection = map.entrySet();
		}
		//這種判斷數組的方式是不適合基本類型數組的,
		//同時Arrays.asList方法接收的是可變參數Object...因此對於基本類型的數組是沒有效果的
		/*if(items instanceof Object[]){
			Object[] obj = (Object[])items;
			collection = Arrays.asList(obj);
		}*/
		//使用反射技術能夠判斷基本類型的數據類型
		if(items.getClass().isArray()){
			int len = Array.getLength(items);
			collection = new ArrayList();
			for(int i=0;i<len;i++){
				collection.add(Array.get(items, i));
			}
		}
	}

	public void setVar(String var) {
		this.var = var;
	}

	@Override
	public void doTag() throws JspException, IOException {
		Iterator it = (Iterator) collection.iterator();
		while(it.hasNext()){
			Object value = it.next();
			this.getJspContext().setAttribute(var, value);
		}
		this.getJspBody().invoke(null);
	}

}


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

<tag>
    <name>forEach</name>
    <tag-class>com.weijia.foreach.ForEachTag</tag-class>
    <body-content>scriptless</body-content>
    <attribute>
        <name>var</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>items</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
  </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:

package org.apache.taglibs.standard.tag.rt.core;

import java.util.ArrayList;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.jstl.core.LoopTag;
import javax.servlet.jsp.tagext.IterationTag;
import org.apache.taglibs.standard.tag.common.core.ForEachSupport;

public class ForEachTag extends ForEachSupport
  implements LoopTag, IterationTag
{
  public void setBegin(int paramInt)
    throws JspTagException
  {
    this.beginSpecified = true;
    this.begin = paramInt;
    validateBegin();
  }

  public void setEnd(int paramInt)
    throws JspTagException
  {
    this.endSpecified = true;
    this.end = paramInt;
    validateEnd();
  }

  public void setStep(int paramInt)
    throws JspTagException
  {
    this.stepSpecified = true;
    this.step = paramInt;
    validateStep();
  }

  public void setItems(Object paramObject)
    throws JspTagException
  {
    if (paramObject == null)
      this.rawItems = new ArrayList();
    else
      this.rawItems = paramObject;
  }
}


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

protected ForEachIterator supportedTypeForEachIterator(Object paramObject)
    throws JspTagException
  {
    ForEachIterator localForEachIterator;
    if ((paramObject instanceof Object[]))
      localForEachIterator = toForEachIterator((Object[])paramObject);
    else if ((paramObject instanceof boolean[]))
      localForEachIterator = toForEachIterator((boolean[])paramObject);
    else if ((paramObject instanceof byte[]))
      localForEachIterator = toForEachIterator((byte[])paramObject);
    else if ((paramObject instanceof char[]))
      localForEachIterator = toForEachIterator((char[])paramObject);
    else if ((paramObject instanceof short[]))
      localForEachIterator = toForEachIterator((short[])paramObject);
    else if ((paramObject instanceof int[]))
      localForEachIterator = toForEachIterator((int[])paramObject);
    else if ((paramObject instanceof long[]))
      localForEachIterator = toForEachIterator((long[])paramObject);
    else if ((paramObject instanceof float[]))
      localForEachIterator = toForEachIterator((float[])paramObject);
    else if ((paramObject instanceof double[]))
      localForEachIterator = toForEachIterator((double[])paramObject);
    else if ((paramObject instanceof Collection))
      localForEachIterator = toForEachIterator((Collection)paramObject);
    else if ((paramObject instanceof Iterator))
      localForEachIterator = toForEachIterator((Iterator)paramObject);
    else if ((paramObject instanceof Enumeration))
      localForEachIterator = toForEachIterator((Enumeration)paramObject);
    else if ((paramObject instanceof Map))
      localForEachIterator = toForEachIterator((Map)paramObject);
    else if ((paramObject instanceof String))
      localForEachIterator = toForEachIterator((String)paramObject);
    else
      localForEachIterator = toForEachIterator(paramObject);
    return localForEachIterator;
  }


咱們發現他會對每一個傳遞進來的對象進行判斷,而後進行一些操做。同時對基本類型進行判斷,可是咱們發現這樣的代碼是有點很差看,咱們爲了體現出咱們的技術,咱們這裏能夠將代碼改一下,而且是實現的比他還要好,就是如下的代碼段:

//使用反射技術能夠判斷基本類型的數據類型
if(items.getClass().isArray()){
	int len = Array.getLength(items);
	collection = new ArrayList();
	for(int i=0;i<len;i++){
		collection.add(Array.get(items, i));
	}
}
這裏咱們使用反射技術來判斷是否是數組類型,這裏能夠判斷是基本類型數組仍是對象類型數組,而後再使用Array這個工具類進行操做數組中的元素,這樣咱們看到這樣的代碼就比jstl中的代碼簡介明瞭,並且技術上也體現出一點高超。


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

<tag>
    <name>forEach</name>
    <tag-class>com.weijia.foreach.ForEachTag</tag-class>
    <body-content>scriptless</body-content>
    <attribute>
        <name>var</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>items</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>

在Jsp頁面中使用:

 <%
		List list = new ArrayList();
		list.add("aaa");
		list.add("bbb");
		String[] strAry = new String[]{"aaa","bbb","ccc"};
		int[] intAry = new int[]{1,2,3,6};
	 %>
	
	<c:forEach var="item" items="<%=list%>">
		${item}
	</c:forEach>
	
	<br>
	
	<c:forEach var="item" items="<%=strAry%>">
		${item}
	</c:forEach>
	
	<br>
	
	<c:forEach var="item" items="<%=intAry%>">
		${item}
	</c:forEach>

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


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


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

一、控制標籤體是否輸出

二、控制標籤體重複輸出

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

四、修改標籤體內容

其實咱們會發現,上面實現的If標籤其實就是第一個案例的體現,choose/when/otherwise標籤就是第一個案例的體現,可是這裏面還有一個功能就是父標籤的編寫,forEach標籤是第二個案例的實現以及第四個案例的實現。因此說咱們爲何一開始要介紹那四個案例,實際上是爲這部份內容作鋪墊的。


下面咱們來進行打包操做了,咱們須要將咱們定義的tld文件一塊兒打包,這個打包也是很簡單的,咱們只須要新建一個Java項目,將咱們定義好的標籤類都拷貝過去,同時在項目中新建一個META-INF文件夾,在將咱們定義的的tld文件拷貝進去,雖然會提示不少錯誤(由於是Java項目,不是Web項目不少類是找不到的)可是咱們不理會,由於咱們知道咱們的類的邏輯和語法是沒有錯誤的,只是找不到相應的類,這時候咱們進行打包,必定要將META-INF文件夾一塊兒打包進去,這時候咱們就可使用這個咱們本身定義的標籤庫包了。


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

相關文章
相關標籤/搜索