摘要:首先我們來(lái)看一下接口的源碼上面程序的執(zhí)行流程引擎遇到自定義標(biāo)簽,首先創(chuàng)建標(biāo)簽處理器類(lèi)的實(shí)例對(duì)象。當(dāng)容器執(zhí)行到自定義標(biāo)簽的結(jié)束標(biāo)記時(shí),調(diào)用方法。
為什么要使用自定義標(biāo)簽?
JSTL標(biāo)簽庫(kù)只提供了簡(jiǎn)單的輸出等功能,沒(méi)有實(shí)現(xiàn)任何的HTML代碼封裝,并且某些復(fù)雜類(lèi)型轉(zhuǎn)換,或者邏輯處理的時(shí)候,JSTL標(biāo)簽庫(kù)完成不了,需要自定義標(biāo)簽!
編寫(xiě)自定義標(biāo)簽的步驟:編寫(xiě)一個(gè)實(shí)現(xiàn)Tag接口的Java類(lèi)【標(biāo)簽處理器類(lèi)】
在WEB-INF目錄下創(chuàng)建tld(Tag Library Descriptor)文件,在tld文件中對(duì)標(biāo)簽處理類(lèi)(實(shí)現(xiàn)Tag接口的Java類(lèi))進(jìn)行描述
快速入門(mén)目標(biāo):使用標(biāo)簽輸出客戶機(jī)的IP地址!
按照步驟來(lái):首先編寫(xiě)一個(gè)實(shí)現(xiàn)Tag接口的Java類(lèi)
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() { } }
既然要獲取到客戶機(jī)的IP地址,那么request對(duì)象是必不可少的。現(xiàn)在問(wèn)題來(lái)了,在Tag重寫(xiě)的方法好像不能直接獲取到request對(duì)象啊。
經(jīng)過(guò)我一番仔細(xì)的觀察,發(fā)現(xiàn)了下面這個(gè)方法:
@Override public void setPageContext(PageContext pageContext) { }
既然能獲取到pageContext對(duì)象,那么其他8大內(nèi)置對(duì)象還不是隨隨便便?于是乎,我就定義一個(gè)成員變量pageContext,在setPageContext()方法中傳遞過(guò)來(lái)的pageContext賦值給我定義的成員變量即可!
private PageContext pageContext = null; @Override public void setPageContext(PageContext pageContext) { this.pageContext = pageContext; }
好的,看回我們的需求:使用標(biāo)簽輸出客戶機(jī)的IP地址。在上面剩余5個(gè)方法中,最有可能就是在doStartTag()方法中編寫(xiě)代碼!
@Override public int doStartTag() throws JspException { //獲取到request對(duì)象 HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest(); //獲取到客戶機(jī)的ip地址 String ip = httpServletRequest.getRemoteAddr(); //獲取輸出到瀏覽器的對(duì)象 JspWriter jspWriter = pageContext.getOut(); //下面的異常只能捕獲,因?yàn)樽宇?lèi)的異常不能比父類(lèi)多 try { jspWriter.write(ip); } catch (IOException e) { e.printStackTrace(); } return 0; }
接著,編寫(xiě)tld文件,描述實(shí)現(xiàn)Tag接口的Java類(lèi)【標(biāo)簽處理類(lèi)】。
1.0 zhongfucheng /zhongfucheng viewIp tag.showIp empty
下面我們來(lái)測(cè)試一下看能不能用
標(biāo)簽處理類(lèi)詳細(xì)說(shuō)明看完上面的程序,大部分人都是懵逼的。因?yàn)檫€不知道它具體是怎么用的,調(diào)用順序是什么。
首先我們來(lái)看一下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(); }
上面程序的執(zhí)行流程:
JSP引擎遇到自定義標(biāo)簽,首先創(chuàng)建標(biāo)簽處理器類(lèi)的實(shí)例對(duì)象。
JSP引擎實(shí)例化完標(biāo)簽處理器類(lèi)后,調(diào)用setPageContext()方法,將pageContext對(duì)象傳遞給標(biāo)簽處理器類(lèi),使得標(biāo)簽處理器類(lèi)可以通過(guò)pageContext對(duì)象與JSP頁(yè)面進(jìn)行通信!
setPageContext()方法執(zhí)行完后,調(diào)用setParent()方法,將當(dāng)前標(biāo)簽的父標(biāo)簽傳遞給當(dāng)前處理器類(lèi),如果當(dāng)前標(biāo)簽沒(méi)有父標(biāo)簽,則傳入null
當(dāng)WEB容器執(zhí)行到自定義標(biāo)簽的開(kāi)始標(biāo)記時(shí),調(diào)用doStartTag()方法。
當(dāng)WEB容器執(zhí)行到自定義標(biāo)簽的結(jié)束標(biāo)記時(shí),調(diào)用doEndTag()方法。
一般來(lái)說(shuō),當(dāng)WEB容器執(zhí)行完自定義標(biāo)簽后,標(biāo)簽處理器類(lèi)會(huì)駐留在內(nèi)存中,直至停止WEB應(yīng)用時(shí),WEB容器才會(huì)調(diào)用release()方法
我們現(xiàn)在已經(jīng)清楚了方法的執(zhí)行順序了,可Tag接口的源碼還有4個(gè)變量阿,它們是用來(lái)做什么的呢?我們?cè)诰帉?xiě)JSP頁(yè)面時(shí),經(jīng)常需要在頁(yè)面中引入一些邏輯,例如:
控制JSP頁(yè)面某一部分(標(biāo)簽體)是否執(zhí)行
控制整個(gè)JSP頁(yè)面是否執(zhí)行
控制JSP頁(yè)面內(nèi)容重復(fù)執(zhí)行
修改JSP頁(yè)面內(nèi)容輸出
再看回4個(gè)變量的名字,我們可以發(fā)現(xiàn),這4個(gè)變量就是用來(lái)做邏輯判斷的!
我們來(lái)測(cè)試一下吧,在doEndTag()方法中,返回的是SKIP_PAGE變量,看下會(huì)怎么樣!
@Override public int doEndTag() throws JspException { return SKIP_PAGE; }
我們?cè)賮?lái)看一看效果:
好像是沒(méi)什么區(qū)別!我們?cè)俨榭匆幌略创a,發(fā)現(xiàn)執(zhí)行完標(biāo)簽后,后面的代碼全都沒(méi)有執(zhí)行!
doStartTag()方法使用的是SKIP_BODY和EVAL_BODY_INCLUDE這兩個(gè)變量,判斷是否執(zhí)行標(biāo)簽體的內(nèi)容。
doEndTag()方法使用的是SKIP_PAGE和EVAL_PAGE這兩個(gè)變量,判斷是否執(zhí)行剩下頁(yè)面的內(nèi)容
控制JSP頁(yè)面內(nèi)容重復(fù)執(zhí)行和修改JSP頁(yè)面內(nèi)容輸出后面會(huì)有!
tld文件詳細(xì)說(shuō)明首先我們來(lái)看一下tld文件當(dāng)前用到的內(nèi)容吧!
1.0 myshortname http://mycompany.com
我們一個(gè)一個(gè)來(lái)看:
shortname推薦使用prefix
uri就是引入這個(gè)標(biāo)簽庫(kù)使用的uri
name為標(biāo)簽名
tagclass為實(shí)現(xiàn)類(lèi)
bodycontent為標(biāo)簽體的限制,它有4個(gè)值: EMPTY【不允許有標(biāo)簽體】,JSP【允許有JSP代碼】 ,scriptless【不允許有腳本代碼(也就是<%%>),允許有EL表達(dá)式,文本,JSP行為】 , tagdepentend【標(biāo)簽體內(nèi)的JSP代碼不會(huì)被解析,直接輸出文本】
TagSupport類(lèi)大部分時(shí)候我們都不需要實(shí)現(xiàn)Tag接口來(lái)編寫(xiě)自定義標(biāo)簽,TagSupport是Tag的一個(gè)模板類(lèi),實(shí)現(xiàn)了pageContext,parent的getter、setter方法以及一些其他的功能。我們要做的就是重寫(xiě)doStartTag()和doEndTag()方法
下面我們就來(lái)簡(jiǎn)單使用一下吧:
繼承TagSupport類(lèi),重寫(xiě)doStartTag()方法,比直接實(shí)現(xiàn)Tag接口簡(jiǎn)潔很多!
public class Demo1 extends TagSupport { @Override public int doStartTag() throws JspException { //獲取到request對(duì)象 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文件中描述一把:
showMethod tag.Demo1 empty
效果:
帶屬性的標(biāo)簽上面我們編寫(xiě)的自定義標(biāo)簽都沒(méi)有附帶屬性的,我們?cè)谑褂胏ore標(biāo)簽庫(kù)的時(shí)候,標(biāo)簽一般都帶有屬性。
其實(shí)JSTL標(biāo)簽庫(kù)的原理就是自定義標(biāo)簽,把自定義標(biāo)簽搞明白了,對(duì)JSTL標(biāo)簽庫(kù)的使用就有更好的理解了!
想要自定義標(biāo)簽帶有屬性也非常簡(jiǎn)單,只要在標(biāo)簽處理器類(lèi)上加一個(gè)成員變量和setter、getter(),再在tld文件中描述下該屬性即可!它的原理是這樣的:當(dāng)標(biāo)簽使用到屬性的時(shí)候,引擎就會(huì)調(diào)用它的setter()方法
下面我想要完成的功能是:使用標(biāo)簽的人,傳入一個(gè)字符串格式就可以顯示想要的格式日期
編寫(xiě)標(biāo)簽處理器類(lèi),增加一個(gè)成員變量以及對(duì)應(yīng)的setter、getter方法
public class Demo1 extends TagSupport { //創(chuàng)建成員對(duì)象,對(duì)應(yīng)的setter、getter方法 private String format = null; @Override public int doStartTag() throws JspException { //創(chuàng)建日期格式化對(duì)象 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); //格式化日期并向?yàn)g覽器輸出 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文件中描述標(biāo)簽和屬性,name代表的是屬性的名字,required代表的是是否為必須,rtexprvalue代表能否使用EL表達(dá)式
formatDate tag.Demo1 empty format true true
我們來(lái)看一下效果:
標(biāo)簽的繼承關(guān)系在深入講解之前,我們先來(lái)看一下各種Tag接口、類(lèi)之間的關(guān)系,這樣學(xué)習(xí)下去才不會(huì)暈!
IterationTag說(shuō)明我們已經(jīng)使用過(guò)了Tag接口和TagSupport類(lèi)了。接下來(lái)我們看一下IterationTag是什么玩意。
public interface IterationTag extends Tag { int EVAL_BODY_AGAIN = 2; int doAfterBody() throws JspException; }
從關(guān)系圖我們也可以看出,IterationTag接口實(shí)現(xiàn)了Tag接口,InterationTag接口和Tag接口最主要的區(qū)別就是多了個(gè)doAfterBody()方法和EVAL_BODY_AGAIN變量
理解起來(lái)也很簡(jiǎn)單:當(dāng)doAfterBody()返回的是EVAL_BODY_AGAIN變量,那么標(biāo)簽體的內(nèi)容就一直循環(huán)!當(dāng)然了,TagSupport也實(shí)現(xiàn)了Iteration接口,也就是說(shuō)TagSupport類(lèi)也能完成Iteration接口的事情!
我們來(lái)使用一下吧:
public class Demo1 extends TagSupport { @Override public int doStartTag() throws JspException { try { pageContext.getOut().write("hello"); } catch (IOException e) { e.printStackTrace(); } //執(zhí)行標(biāo)簽體 return EVAL_BODY_INCLUDE; } @Override public int doAfterBody() throws JspException { //標(biāo)簽體不斷循環(huán),直到doAfterBody()返回的是SKIP_BODY return EVAL_BODY_AGAIN; } }
tld文件中描述,既然標(biāo)簽體有內(nèi)容,就不能用empty了!
foreverEval tag.Demo1 tagdependent
注意看橫向的滑輪,已經(jīng)死循環(huán)輸出了:
doAfterBody()中只要返回的是SKPI_BODY就退出循環(huán),執(zhí)行doEndTag()方法
//定義一個(gè)變量,規(guī)定標(biāo)簽體循環(huán)的次數(shù) int x = 0; @Override public int doStartTag() throws JspException { try { pageContext.getOut().write("hello"); } catch (IOException e) { e.printStackTrace(); } //執(zhí)行標(biāo)簽體 return EVAL_BODY_INCLUDE; } @Override public int doAfterBody() throws JspException { x++; if (x >= 10) { return SKIP_BODY; } //標(biāo)簽體不斷循環(huán),直到doAfterBody()返回的是SKIP_BODY return EVAL_BODY_AGAIN; }
現(xiàn)在我們已經(jīng)能控制循環(huán)的次數(shù)了!
BodyTag說(shuō)明前面我們已經(jīng)使用到了帶標(biāo)簽體的自定義標(biāo)簽了,前面的都是只能直接輸出而得不到標(biāo)簽體的內(nèi)容,既然得不到標(biāo)簽體的內(nèi)容,就更別說(shuō)修改標(biāo)簽體了!
此時(shí),我們就需要BodyTag接口的支持了!它專(zhuān)門(mén)用來(lái)處理帶標(biāo)簽體的標(biāo)簽,下面我們來(lái)看一下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變量【一個(gè)已經(jīng)標(biāo)識(shí)過(guò)時(shí)了】,多了setBodyContent和doInitBody()兩個(gè)方法
其實(shí)使用BodyTag十分簡(jiǎn)單
如果doStartTag()方法返回的是EVAL_BODY_BUFFERED,把標(biāo)簽體的內(nèi)容緩存起來(lái)
接著調(diào)用setBodyContent()方法和doInitBody()方法,封裝標(biāo)簽體的內(nèi)容到BodyContent對(duì)象中
接著調(diào)用doEndTag()方法
對(duì)于標(biāo)簽體的內(nèi)容,我們可以通過(guò)getBodyContenet()來(lái)獲取!
再看回上面的關(guān)系圖,BodyTag實(shí)現(xiàn)了IterationTag和Tag接口,如果直接實(shí)現(xiàn)BodyTag接口做開(kāi)發(fā),要實(shí)現(xiàn)的方法就太多了。一般我們使用繼承BodyTag的BodyTagSupport來(lái)做開(kāi)發(fā)
BodyTagSupport說(shuō)明首先來(lái)看一下源代碼吧:
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(); } }
可以發(fā)現(xiàn):BodyTagSupport主要擴(kuò)充了以下的內(nèi)容:
把BodyContent直接定義為成員變量,在獲取標(biāo)簽體內(nèi)容的時(shí)候就不需要通過(guò)getBodyContent()獲取了
提供獲取JspWriter的方法,不需要從pageConext中獲取了
以上的兩個(gè)擴(kuò)充都簡(jiǎn)化了我們的代碼書(shū)寫(xiě)!
protected BodyContent bodyContent; public JspWriter getPreviousOut() { return this.bodyContent.getEnclosingWriter(); }
從BodyTag接口中,我就說(shuō)到了:標(biāo)簽體的內(nèi)容封裝到了BodyContent類(lèi)中,那么BodyContent類(lèi)究竟是什么?我們來(lái)看一下源碼:
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; } }
原來(lái)BodyContent繼承著JspWriter,它與JspWriter最大的區(qū)別是:BodyContent類(lèi)的任何寫(xiě)入的內(nèi)容并不自動(dòng)地向頁(yè)面輸出!
我們一般使用BodyContent都使用兩個(gè)方法:
//將數(shù)據(jù)轉(zhuǎn)變成Reader對(duì)象 public abstract Reader getReader(); //將數(shù)據(jù)轉(zhuǎn)變成String對(duì)象 public abstract String getString();
再?gòu)年P(guān)系圖我們可以看初,BodyTagSupport繼承了TagSupport類(lèi)實(shí)現(xiàn)了BodyTag接口,可以說(shuō):BodyTagSupport有著前面講的接口和類(lèi)的所有功能!。
下面我們來(lái)使用下BodyTagSupport將標(biāo)簽體的內(nèi)容轉(zhuǎn)成是小寫(xiě)的:
標(biāo)簽處理器類(lèi)
public class Demo1 extends BodyTagSupport { @Override public int doStartTag() throws JspException { //想要獲取到標(biāo)簽體的內(nèi)容,就要返回EVAL_BODY_BUFFERED變量 return EVAL_BODY_BUFFERED; } @Override public int doEndTag() throws JspException { //獲取到標(biāo)簽體的內(nèi)容 String value = bodyContent.getString(); //將標(biāo)簽體的內(nèi)容轉(zhuǎn)成小寫(xiě)并輸出 try { this.getPreviousOut().write(value.toLowerCase()); } catch (IOException e) { e.printStackTrace(); } return super.doEndTag(); } }
tld文件:
BodyContentToLowerCase tag.Demo1 tagdependent
效果:
如果文章有錯(cuò)的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章的同學(xué),可以關(guān)注微信公眾號(hào):Java3y.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/68456.html
摘要:前言由于寫(xiě)的文章已經(jīng)是有點(diǎn)多了,為了自己和大家的檢索方便,于是我就做了這么一個(gè)博客導(dǎo)航。 前言 由于寫(xiě)的文章已經(jīng)是有點(diǎn)多了,為了自己和大家的檢索方便,于是我就做了這么一個(gè)博客導(dǎo)航。 由于更新比較頻繁,因此隔一段時(shí)間才會(huì)更新目錄導(dǎo)航哦~想要獲取最新原創(chuàng)的技術(shù)文章歡迎關(guān)注我的公眾號(hào):Java3y Java3y文章目錄導(dǎo)航 Java基礎(chǔ) 泛型就這么簡(jiǎn)單 注解就這么簡(jiǎn)單 Druid數(shù)據(jù)庫(kù)連接池...
摘要:到目前為止,使用越來(lái)越廣泛,不光光只是它強(qiáng)大的生成技術(shù),而且它能夠與進(jìn)行很好的集成。注意使用數(shù)字范圍來(lái)定義集合時(shí)無(wú)需使用方括號(hào)數(shù)字范圍也支持反遞增的數(shù)字范圍如對(duì)象對(duì)象使用花括號(hào)包括中的對(duì)之間以英文冒號(hào)分隔,多組對(duì)之間以英文逗號(hào)分隔。 Freemarker的介紹 ??Freemarker 是一款模板引擎,是一種基于模版生成靜態(tài)文件的通用 工具,它是為程序員提供的一個(gè)開(kāi)發(fā)包,或者說(shuō)是一個(gè)類(lèi)...
摘要:傳統(tǒng)標(biāo)簽是這樣子的將標(biāo)簽體的內(nèi)容通過(guò)注入到對(duì)象中。現(xiàn)在我們使用標(biāo)簽來(lái)進(jìn)行防盜鏈模擬下場(chǎng)景頁(yè)面是海賊王資源,頁(yè)面提示非法盜鏈,是我的首頁(yè)。 為什么要用到簡(jiǎn)單標(biāo)簽? 上一篇博客中我已經(jīng)講解了傳統(tǒng)標(biāo)簽,想要開(kāi)發(fā)自定義標(biāo)簽,大多數(shù)情況下都要重寫(xiě)doStartTag(),doAfterBody()和doEndTag()方法,并且還要知道SKIP_BODY,EVAL_BODY等等的變量代表著什么,...
摘要:大家好鴨,我又來(lái)更新啦還記得我們?cè)诘诙坛讨刑岬竭^(guò)的動(dòng)作嗎,今天我們就來(lái)專(zhuān)門(mén)講講在中的,學(xué)習(xí)不同類(lèi)型的動(dòng)作對(duì)應(yīng)的應(yīng)用場(chǎng)景,并且在我們的應(yīng)用中使用上其中一些類(lèi)型的動(dòng)作。報(bào)表動(dòng)作這類(lèi)型的動(dòng)作用于觸發(fā)報(bào)表打印,例如打印發(fā)票等。 showImg(https://segmentfault.com/img/bVbhdTE?w=1471&h=845); 大家好鴨,我又來(lái)更新啦!還記得我們?cè)诘诙?..
閱讀 752·2021-09-28 09:35
閱讀 2591·2019-08-29 11:25
閱讀 2154·2019-08-23 18:36
閱讀 1849·2019-08-23 16:31
閱讀 2065·2019-08-23 14:50
閱讀 3112·2019-08-23 13:55
閱讀 3286·2019-08-23 12:49
閱讀 2074·2019-08-23 11:46