EL表達(dá)式與JSP標(biāo)簽(學(xué)習(xí)筆記)

??本文內(nèi)容主要介紹EL表達(dá)式、JSP標(biāo)準(zhǔn)標(biāo)簽庫(JSTL)、JSP自定義標(biāo)簽庫。其中拿EL表達(dá)式和JSP技術(shù)做對(duì)比。而JSP標(biāo)準(zhǔn)標(biāo)簽庫和JSP的自定義標(biāo)簽內(nèi)容都是較為粗略地帶過。


EL標(biāo)簽

??EL表達(dá)式我的理解是能夠方便地從request、session等各個(gè)域中獲取變量值。在這之前,我們通過JSP技術(shù)來獲取這些變量值,使用JSP技術(shù)和使用EL表達(dá)式的示例代碼分別如下所示:

    <body>
        用戶名:<%=request.getAttribute("username")%>
        密碼:<%= request.getAttribute("password")%>
    </body>

使用了EL表達(dá)式的代碼如下所示:

    <body>
        用戶名:${username}
        密碼:${password}
    </body>

??由此可見,使用EL表達(dá)式來獲取變量比使用JSP技術(shù)要簡潔得多。在上例中,使用JSP技術(shù)獲取變量時(shí),需要使用隱式對(duì)象(如這里的request)調(diào)用getAttribute()方法來實(shí)現(xiàn),而EL表達(dá)式不需要調(diào)用隱式對(duì)象。

??說到隱式對(duì)象,JSP有9個(gè)隱式對(duì)象,而EL有11個(gè)隱式對(duì)象,但是這不代表EL就有更多的功能。事實(shí)上,EL隱式對(duì)象中,param、paramValues、header、headerValues、cookie都是針對(duì)request而言的,也就是說這五個(gè)隱式對(duì)象都是為了獲取請(qǐng)求信息的方便。所以單獨(dú)從功能的實(shí)現(xiàn)不考慮便捷程度的話,EL中的requestScope已經(jīng)可以實(shí)現(xiàn)這四個(gè)隱式對(duì)象的功能。11-5=6,這么一算,剩下六個(gè)隱式對(duì)象,反而比JSP的9個(gè)隱式對(duì)象要少三個(gè)。
??其中JSP中的config對(duì)應(yīng)EL中的initParam。
??少了哪一些呢,少了out、response、exception。我們可以看到,少了有兩個(gè)用于輸出和響應(yīng)的。而在這方面EL表達(dá)式并沒有對(duì)應(yīng)的隱式對(duì)象。所以我的理解是EL表達(dá)式中隱式對(duì)象的目的在于簡化對(duì)request對(duì)象的信息獲取過程。而輸出和報(bào)錯(cuò)等功能,我們?nèi)耘f可以沿用JSP技術(shù)提供的內(nèi)容,并不會(huì)顯得復(fù)雜。
??EL表達(dá)式和JSP技術(shù)的隱式對(duì)象中,pageContext是二者所共有的。在EL表達(dá)式中,也可以通過pageContext.request來訪問到本屬于JSP技術(shù)的隱式對(duì)象。它的好處無需調(diào)用getAttribute()的方法,這時(shí)候可以寫成${pageContext.request.contentType}。
??JSP技術(shù)中是通過調(diào)用getAttribute()的方法來實(shí)現(xiàn)的,寫法繁雜。并且如果采用這種方法,我們就必須先選擇我們的隱式對(duì)象,需要先明確知道我們的變量存放在哪個(gè)隱式對(duì)象中。而在EL表達(dá)式中,可以不指定查找域,直接引用屬性名就可以,然后會(huì)在page、request、session、application這四個(gè)作用域內(nèi)按順序依次進(jìn)行查找。
??而現(xiàn)在可以說我們就只需要關(guān)注變量本身了,只需要知道變量名,就能夠獲取變量值

JSP標(biāo)準(zhǔn)標(biāo)簽庫

JSTL =Java Server Pages Standard Tag Library
JSTL雖然被稱為標(biāo)準(zhǔn)標(biāo)簽庫,但是這個(gè)標(biāo)簽庫是由五個(gè)不同功能的標(biāo)簽庫共同組成的。

標(biāo)簽庫 前綴
Core c
I18N fmt
SQL sql
XML x
Functions fn

??從名字上也不難猜出來,這些標(biāo)簽庫具有不同的功能。其中Core是核心標(biāo)簽庫,包含web應(yīng)用中通用操作的標(biāo)簽。SQL是一個(gè)數(shù)據(jù)庫標(biāo)簽庫,包含用于訪問數(shù)據(jù)庫和對(duì)數(shù)據(jù)庫中的數(shù)據(jù)庫進(jìn)行操作的標(biāo)簽(但是比較少用,因?yàn)橐话悴粫?huì)直接在jsp頁面操作數(shù)據(jù)庫)。而XML則是用于操作XML文檔的標(biāo)簽庫。

??這里我們先來搞清楚EL表達(dá)式JSP技術(shù)、JSP標(biāo)簽概念和結(jié)構(gòu)上的差別。
??首先上面說的EL是表達(dá)式,不用放在標(biāo)簽中。JSP技術(shù)要求將代碼寫在<% %>這個(gè)“標(biāo)簽”里面,但這是為了將Java代碼和Html頁面的其他內(nèi)容區(qū)分開所使用的方法,不能叫JSP標(biāo)簽,只能叫JSP技術(shù)。而這里的JSP標(biāo)簽則是真真正正使用標(biāo)簽對(duì)的方法。
??本來,我們理所應(yīng)當(dāng)認(rèn)為JSP標(biāo)簽對(duì)應(yīng)該是使用JSP技術(shù)來實(shí)現(xiàn)的。但卻發(fā)現(xiàn),在JSP標(biāo)簽中使用的是EL技術(shù)來獲取變量,而不是JSP技術(shù)。例如:

<c:out value="${param.username}">unknown</c:out>

所以可以說, JSP標(biāo)簽使用了EL技術(shù)獲取變量,EL技術(shù)替代了使用JSP技術(shù)獲取變量的方法。本來直接使用JSP獲取變量就可以了,這里使用EL技術(shù)是為了方便。當(dāng)然如果有其他方面的需求,還是要利用JSP中的其他技術(shù)。例如當(dāng)變量不存在session等域中,這時(shí)候還是要用JSP技術(shù)才能獲取到該變量。例如創(chuàng)建一個(gè)對(duì)象或者創(chuàng)建一個(gè)Map對(duì)象。如果不將其保存到session等域中,那么JSTL標(biāo)簽是沒有辦法獲取的。**

    <%
        String [] fruits= {"apple","orange","grape","banana"};
    %>

    <c:foreach var="name" items="<%= fruits %>">
        ${name}<br>
    </c:foreach>    

??在上面的代碼中,無論是創(chuàng)建數(shù)組,還是引用都是用jsp技術(shù)。為什么引用對(duì)象不用EL表達(dá)式${fruits}呢。這是因?yàn)镴STL標(biāo)簽雖然獲取變量方便,但是它能夠獲取的只是request、session、context、application等域的變量。(當(dāng)然現(xiàn)在有了<c:set>標(biāo)簽,也能在jsp頁面新建一些變量,這部分也能夠獲取但是如果本來就是在<% %>創(chuàng)建的數(shù)組fruits,而這個(gè)數(shù)組有沒有保存在某個(gè)域中,那么自然就只能夠通過<% %>方式來獲取了,也就是說只能使用回JSP技術(shù)了。

??如果說EL技術(shù)滿足了我們讀取信息的需求,并且讓我們?cè)谧x取信息的時(shí)候可以更專注于變量本身,而不需要去通過域?qū)ο螳@取的話,那么Core標(biāo)簽庫在一定程度上就滿足了我們輸出、更改等方面的需求。
修改javabean中的屬性的方法見下例:

<c:set value="itcast" target="${user}" property="username"/>

??先通過找到user對(duì)象,然后再對(duì)其屬性進(jìn)行更改。
除此以外,Core標(biāo)簽庫還有<c:out><c:set><c:remove><c:catch>等標(biāo)簽,我們也就發(fā)現(xiàn)原來JSP多出來而EL中少的幾個(gè)隱式對(duì)象(out、response、exception),都可以在Core標(biāo)簽庫中找到更便捷的解決方法。而且除了這些功能外,還能進(jìn)行一些簡單的邏輯判斷(<c:if><c:choose>等標(biāo)簽)。
??而JSTL中的function標(biāo)簽庫則是為了簡化JSP頁面對(duì)字符串的操作。例如fn:split用于對(duì)字符串進(jìn)行分割,而fn:join用于將字符串?dāng)?shù)組合并成一個(gè)字符串。并且這個(gè)function標(biāo)簽庫雖然說是標(biāo)簽庫,但其實(shí)也沒有放在標(biāo)簽里面,更類似于EL表達(dá)式,存在于花括號(hào)中。
??說實(shí)在話,上面的標(biāo)簽看起來功能很多,但是主要也只是完成了屬性的獲取和字符串的操作這兩個(gè)功能。還有許許多多無能為力的方面。
而這些功能可以通過自定義標(biāo)簽來實(shí)現(xiàn)。我們可以說自定義標(biāo)簽庫就是一段段小代碼。有什么好處呢。首先將代碼對(duì)應(yīng)成標(biāo)簽,然后調(diào)用標(biāo)簽來進(jìn)行執(zhí)行,本身就可以減少很多的代碼重復(fù)。
??另一方面,可以結(jié)合JSP技術(shù)和EL技術(shù),換句話說,可以很方便地獲取到應(yīng)用內(nèi)的各個(gè)變量,也降低了代碼量。之前說過,不希望JSP頁面出現(xiàn)太多的代碼,以往的解決方法是將代碼部分都轉(zhuǎn)移到其他的Servlet中,然后將結(jié)果返回到JSP頁面,JSP頁面只執(zhí)行顯示功能,沒有太多定制功能。而現(xiàn)在則是利用標(biāo)簽來減少代碼量,JSP頁面本身就能夠定制結(jié)果的顯示,甚至能執(zhí)行一定的功能。

JSP自定義標(biāo)簽庫

??JSP自定義標(biāo)簽庫功能強(qiáng)大,但是可以簡單理解為將Java代碼以“標(biāo)簽庫”的方式進(jìn)行引用。這部分內(nèi)容也有很多容易混淆的地方,而且剛開始接觸,所以這里只是簡單的做一些學(xué)習(xí)的記錄(甚至都稱不上總結(jié)),后面用到的時(shí)候再回頭看看吧。
??開發(fā)自定義標(biāo)簽需要編寫作為標(biāo)簽處理器的Java類,還要編寫一個(gè)標(biāo)簽庫描述符(Tag Library Descriptor)文件,簡稱TLD文件(TLD文件和標(biāo)簽處理器之間的關(guān)系就如同web.xml文件與Servlet之間的關(guān)系)。然后就可以通過下述代碼在JSP文件中引入:

<%@ taglib uri = "" prefix = "" %>

??其中uri的值和TLD文件中<uri>元素的值要保持一致。
??自定義標(biāo)簽開發(fā)又分為傳統(tǒng)標(biāo)簽開發(fā)和簡單標(biāo)簽開發(fā)。首先,無論是傳統(tǒng)標(biāo)簽還是簡單標(biāo)簽,都需要實(shí)現(xiàn)Tag接口。它定義了4個(gè)int類型的靜態(tài)常量和6個(gè)抽象方法。
setPageContext()-->setParent()-->getParent()-->doStartTag()-->doEndTag()-->release()
??其中doStartTag()用于判斷是否執(zhí)行標(biāo)簽體內(nèi)容,而doEndTag用于判斷是否繼續(xù)執(zhí)行標(biāo)簽后面的JSP內(nèi)容。
??傳統(tǒng)標(biāo)簽開發(fā)中IterationTag接口是針對(duì)標(biāo)簽體的內(nèi)容重復(fù)處理所設(shè)計(jì)的接口。在Tag接口上新增了一個(gè)doAfterBody()方法。TagSupprot類是該接口的一個(gè)實(shí)現(xiàn)類。

public class Iterate extends TagSupport{
    ...
    private int num;
    public int doAfterBody() throws JspException{
        num --;
        if (num > 0){
            return EVAL_BODY_AGAIN;
        }else{
            return SKIP_BODY;
        }
    }
}

??而繼承自IterationTag接口的BodyTag接口除了能進(jìn)行重復(fù)顯示,還能夠?qū)?biāo)簽體的內(nèi)容做一些處理,然后再向?yàn)g覽器輸出。
??如果實(shí)現(xiàn)了BodyTag接口(BodyTagSupport是該接口的一個(gè)實(shí)現(xiàn)類),在標(biāo)簽處理器調(diào)用doStartTag()方法時(shí),JSP容器會(huì)調(diào)用setBodyContent()方法,通過該方法將BodyContent對(duì)象傳遞給標(biāo)簽處理器類使用。
而BodyContent就是用于實(shí)現(xiàn)對(duì)標(biāo)簽內(nèi)容處理的功能的。
??傳統(tǒng)標(biāo)簽一般都要實(shí)現(xiàn)doStartTag()和doEndTag()方法。當(dāng)然,如果doStartTag()或者doEndTag()方法不需要進(jìn)行重載,那么就可以不用寫出來。例如在BodyTagSupport類中,doStartTag()方法默認(rèn)會(huì)返回EVAL_BODY_BUFFERD常量,JSP容器就會(huì)在執(zhí)行標(biāo)簽體之前創(chuàng)建BodyContent對(duì)象。所以如無必要,可以只實(shí)現(xiàn)doEndTag()方法。

SimpleTag是所有簡單標(biāo)簽的父接口,它定義了5個(gè)方法(SimpleTagSupport是該接口的一個(gè)實(shí)現(xiàn)類)。
??而簡單標(biāo)簽則是將傳統(tǒng)標(biāo)簽接口中定義的doStartTag()、doEndTag()和doAfterBody()等方法都集中到doTag()方法來進(jìn)行實(shí)現(xiàn)。
??上面的傳統(tǒng)標(biāo)簽實(shí)現(xiàn)了是否多次顯示標(biāo)簽體和對(duì)標(biāo)簽體進(jìn)行處理的功能。傳統(tǒng)標(biāo)簽中使用BodyTagSupport來實(shí)現(xiàn)獲取標(biāo)簽體的功能,現(xiàn)在則是通過直接調(diào)用getJspBody()來實(shí)現(xiàn)。調(diào)用該方法就可以獲取到一個(gè)JspFragment類的實(shí)例對(duì)象。在標(biāo)簽體的多次顯示功能上,通過invoke()而不是返回變量的方式來決定顯示標(biāo)簽體。
??之前提到的BodyContent對(duì)象具有數(shù)據(jù)緩沖區(qū),并且可以通過getString()等方法來獲取緩沖區(qū)的內(nèi)容。而這里的JspFragment沒有緩存,也沒有g(shù)etString()方法。如果需要對(duì)內(nèi)容進(jìn)行修改的話,需要在調(diào)用invoke()方法的時(shí)候傳入一個(gè)可以取出結(jié)果數(shù)據(jù)的輸出流對(duì)象(例如StringWriter、CharArrayWriter)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容