JSP第六篇【自定義標簽之傳統標簽】
為什麽要使用自定義標簽?
JSTL標簽庫只提供了簡單的輸出等功能,沒有實現任何的HTML代碼封裝,並且某些復雜類型轉換,或者邏輯處理的時候,JSTL標簽庫完成不了,需要自定義標簽!
編寫自定義標簽的步驟:
- 編寫一個實現Tag接口的Java類【標簽處理器類】
- 在WEB-INF目錄下創建tld(Tag Library Descriptor)文件,在tld文件中對標簽處理類(實現Tag接口的Java類)進行描述
快速入門
目標:使用標簽輸出客戶機的IP地址!
按照步驟來:首先編寫一個實現Tag接口的Java類
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變量,看下會怎麽樣!
@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()方法
下面我們就來簡單使用一下吧:
繼承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.
JSP第六篇【自定義標簽之傳統標簽】