1. 程式人生 > >Servlet/JSP代碼之上

Servlet/JSP代碼之上

純粹 resource 應用場景 方法 miss 。。 嘗試 row charset

1 Web應用程序與服務器技術

瀏覽器從服務器取得HTML文件後,按照其中的結構等信息進行畫面的繪制。

URI(URL)的主要目的,是以文字方式來說明因特網上的資源如何取得。

文字格式,協議:特定協議部分

協議(scheme)指定以何種方式取得資源,如ftp,http,file等。

特定協議部分格式為,//用戶:密碼@主機:端口號/路徑

完整例子:

http://zuper.com/missnail/index.htmlfile://C:/workspace/jdbc.pdf

HTTP是一種通信協議,指兩臺計算機之間溝通的方式,瀏覽器跟Web服務器之間的溝通方式便是HTTP。

HTTP兩個基本但最重要的特性:基於請求/響應模型 和 無狀態的通信協議。

靜態網頁,指的是請求服務器上網頁時,服務器不對網頁文件作任何處理,讀取文件之後直接響應給瀏覽器。

動態網頁,指在服務器響應之前,會依客戶端的請求參數、標頭等,以特定的方式動態響應內容,再傳回給用戶。

JSP和JavaScript了解服務端程序和客戶端程序的差別。

Servlet/JSP是服務器上的一種技術,客戶端通過HTTP和網絡傳送請求給Servlet/JSP,服務器上的Servlet/JSP經過處理後,再將響應(包括HTML和JavaScript)傳回給客戶端。

所以,客戶端程序和服務器程序,兩者位於不同的內存地址空間,兩者無法直接互動,必須通過網絡和HTTP來進行互動、數據交換或請求、響應。

JVM是Java程序唯一認識的操作系統,其可執行文件為.class文件。

Web容器是Servlet/JSP唯一認得的HTTP服務器。

容器的概念也用在Java的Collection集合API中,代表持有、保持對象的集合對象。對Servlet/JSP來說,容器概念有所拓展,不僅持有對象,還負責對象的生命周期與相關服務的連接。

在具體層面,容器其實就是Java寫的程序,運行於JVM之上,不同類型的容器會負責不同的工作。

在抽象層面,Servlet/JSP容器則充當了HTTP服務器的角色,將HTTP那些文字性的通信協議變成Servlet/JSP中可用的Java對象,其實就是容器來解剖與轉換的。

就如

JVM介於Java程序與實體操作系統之間,Web容器則介於實體HTTP服務器與Servlet之間。

請求/響應例子:

1)客戶端,一般指瀏覽器,對Web服務器發出HTTP請求;

2)服務器收到請求,將其轉由Web容器處理,容器會剖析HTTP請求內容,創建各種對象,如HttpServletRequest、HttpServletResponse、HttpSession等;

3)Web容器根據請求的URL決定使用哪個Servlet來處理請求(事先由開發人員定義規則);

4)Servlet根據請求對象HttpServletRequest的信息決定如何處理,通過響應對象響應;

5)Web容器與HTTP服務器溝通,Web服務器將響應轉換為HTTP響應並傳回客戶端。

Web容器是由服務器上的JVM啟動,JVM本身就是服務器上的可執行程序,當請求來到時,Web容器會為每個請求分配一個線程,所以會有一個Servlet實例處理多個請求時,線程安全是個主題。

實際上,Servlet和JSP是一體兩面,我們請求JSP頁面時,JSP頁面會由Web容器轉譯成為Servlet,Servlet對請求進行處理後對客戶端輸出為HTML內容,所以JSP是所謂動態網頁。


2 開始Servlet

Tomcat主要提供Web容器的功能,會附帶簡單的HTTP服務器功能。

我們編寫的每個Servlet,都是繼承HttpServlet,繼承依賴如下:

ServletRequest <----- Servlet -----> ServletResponse

GenericServlet 實現 Servlet

HttpServletRequest <----- HttpServlet(繼承GenericServlet) -----> HttPServletResponse

我們的Servlet繼承HttpServlet

當請求來到,容器會調用Servlet的service()方法,而HttpServlet實現的service()方法中,就是判斷HTTP請求的方式,分別調用doGet()、doPost()、doDelete()等方法,所以我們的Servlet繼承HttpServlet後,主要是重寫對應的doGet()和doPost()。--Template Method設計模式

編寫好我們的Servlet後,接下來要告訴Web容器關於這個Servlet的信息,主要是Servlet的name,urlPattern和loadOnstartup。Web容器會自動讀取Servlet的這些信息,在請求來到,對對應的Servlet實例化、初始化操作,然後處理請求。

如果希望應用程序啟動時,就將Servlet類載入、實例化並初始化,就是設置loadOnStartup為大於0的值即可,默認值是-1。

這些Servlet信息,可以通過web.xml(稱為部署描述文件 Deployment Descriptor,簡稱DD文件)配置,也可以用註解配置。部署描述文件的配置項會覆蓋註解設置,我們可以將註解設置作為默認配置,web.xml則作為更改之用。

不管是使用@WebServlet註解,還是使用web.xml配置,這時請求的URL其實是個邏輯名稱,請求/hello.do並不是指服務器上真的有名為hello.do的實體文件,所以如果願意,我們用/hello.jsp之類的名稱來偽裝資源。

IDE為了方便管理,會有項目專屬的文件組織,而真正部署到Web容器的應用程序,必須按照如下特定結構:

HelloServlet 項目名稱

--WEB-INF 對外界封閉的目錄,客戶端無法直接訪問,必須通過Servlet/JSP的請求轉發。

--web.xml 部署描述文件

--lib 放置應用程序依賴JAR文件的目錄

--classes 放置編譯後.class文件的目錄,必須和IDE的src類包目錄結構一致。

--other 用戶其他資源文件和目錄

實際部署時,會將Web應用程序打包為.war文件,是一種zip壓縮文件。

可以使用JDK工具來打包應用程序,進入HelloServlet目錄,執行命令:jar cvf ../HellowServlet.war,將會在HelloServlet目錄外創建HelloServlet.war文件。

如果使用Tomcat,將這.war文件放置在webapps目錄下,重新啟動Tomcat,Web容器發現此目錄下有.war包,會將其解壓縮,並載入Web應用程序。


3 請求與響應

Web容器到Servlet

當請求來到HTTP服務器,服務器轉交請求給Web容器,容器會創建一個代表當次請求的HttpServletRequest對象,並將請求相關信息設置給該對象。

同時,容器會創建一個HTTPServletResponse對象,作為稍後要對客戶端進行響應的Java對象。

接著,容器會讀取@WebServlet註解和web.xml文件的設置,找到處理該請求的Servlet,調用其service()方法,傳入創建好的HttpServletRequest對象和HttpServletResponse對象作為方法參數,service()方法根據HTTP請求的方式,調用對應的doXXX()方法。如果是GET請求,則調用doGet()方法。

doGet()方法中,可以使用HttpServletRequest對象對請求進行處理(getParameter()),使用HttpServletResponse對象取得輸出對象(PrintWritter對象)並進行響應處理。對輸出對象做的輸出操作,最後由容器轉為HTTP響應,再由HTTP服務器對客戶端進行響應。

最後容器將HttpServletRequest對象和HttpServletResponse對象銷毀回收,本次請求/響應結束。

PS:

查詢API可以發現,HttpServletResponse和HttpServletRequest都是接口,而實現這些接口的相關類就是容器提供的,Web容器本身就是一個運行著的用Java編寫的應用程序。

一個HTTP請求通常告知服務器這些信息:請求資源的URL,HTTP版本,標頭(Header),信息體(Message Body)。

GET請求的請求參數會顯示在URL裏面,而POST則是將其作為信息體的內容,標頭內容則是客戶端的一些額外信息,如瀏覽器種類和版本等。

在選擇使用GET和POST請求時,應該主要考慮以下方面:

請求參數是否很多很長,是否上傳文件,請求參數是否涉及安全性,是否要避免瀏覽器緩存。

最初衷的是,請求是否等冪操作,GET應該是等冪操作,純粹從服務器取得資源,而不改變服務器的狀態,如修改服務器數據庫的操作或者是在服務器創建文件等請求,會改變服務器的狀態,應該是用POST。

結論:還有編碼處理等問題,所以直接所有使用POST就好。

關於HttpServletRequest

可以使用HttpServletRequest對象獲取到請求的請求參數、標頭和信息體(包括上傳文件)等信息。

開發時,為避免亂碼問題,應保持請求和響應的編碼一致。客戶端網頁的meta設置請求標頭為UTF-8編碼後,服務端的Servlet/JSP輸出響應時也應該設置編碼為UTF-8。PS:JSP中有兩處編碼項不能混淆,一個是服務端Servlet編碼,一個是HTML客戶端編碼。

有時候,一個請求需要多個Servlet來完成,Servlet需要使用RequestDispatcher進行請求調派。

請求調派的兩種方法類型:

1)include() 包含,將另一個Servlet的操作流程包含到當前Servlet操作流程中。

2)forward() 轉發,將當前Servlet的處理轉發給另一個Servlet,好比跑接力賽,最後一個Servlet才能有響應。

關於HttpServletResponse

可以使用HttpServletResponse對象來對瀏覽器進行響應。

使用setContentType()設置響應類型,一般是setContentType("text/html", charset="UTF-8"),將編碼也設置好,對應JSP中html-meta前的設置。PS:"text/html"是MIME類型的一種,還有application/pdf,application/x-zip,image/jpeg等,新的MIME還在增加。

使用setHeader(),addHeader()等設置響應標頭信息,所有的標頭設置,需在響應確認之前,之後將被容器忽略。

使用sendRedirect()對客戶端要求重定向網頁,即要求瀏覽器重新請求另一個URL,可以指定絕對URL或者相對URL。PS:使用請求對象進行轉發和包含操作時,是在Web容器中進行的,瀏覽器並不知道,所以地址欄不會變化。並且轉發和包含過程中,都還在一個請求周期內,所以設置的請求範圍屬性(req.setAttribute(),req.getAttribute()),可以在轉發和包含過程中共享。重定向則是當前請求響應HTTP狀態碼301以及Location標頭,瀏覽器接受這個標頭,重新使用GET方法請求指定的URL,所以瀏覽器地址欄可以看見變化,過程中是兩次請求。

使用sendError()向客戶端傳送服務端的狀態與錯誤信息,比如404就是response.setError(HttpServletResponse.SC_NOT_FOUND)。由於過程中利用到HTTP狀態碼,要求瀏覽器重定向網頁,所以和重定向一樣,sendError()同樣需在響應未確認輸出前執行,否則拋IllegalStateException異常。

使用getWritter()取得PrintWriter輸出對象,而後使用其println()等方法輸出內容。若有輸出字節數據的需求,可以使用getOutputStream()取得ServletOutputStream輸出對象,直接使用串流對象對瀏覽器進行字節數據的響應(如圖片,PDF文件等)。PS:PrintWriter是字符流。


4 會話管理

會話管理基本原理與方式

由於HTTP是基於請求/響應的無狀態的通信協議,所以服務端不會記得本次請求與下次請求的關系,但是有些需求比如購物車功能等需要對此請求來完成,這種讓服務器記得此次請求與之後請求間關系的方法就稱為會話管理(Session Management)。

實現會話管理的三種基本方式:

1)使用網頁隱藏域

利用下次請求網頁的表單隱藏域,可以保留上次請求的信息,達到效果。

但是客戶關掉網頁後,保留的信息就遺失了,並且查看網頁源代碼就能看的隱藏域的值,所以也不安全。

2)使用URL重寫

即將上次請求的信息拼接到本次請求的URL後當中請求參數,一般是以超鏈接形式再次發送GET請求發出請求。

這種方式優點是簡單,缺點就是GET請求方式的特點了。

3)使用Cookie

Cookie是瀏覽器存儲信息的一種方式。服務器可以響應瀏覽器set-cookie標頭,瀏覽器接受這個標頭與值後,將其以文件的形式存儲在客戶端計算機上,這個文件就成為Cookie。Cookie默認是關閉瀏覽器後失效,我們可以給Cookie設置存活期限,即使客戶關閉了瀏覽器,只要在期限內,下次請求時,瀏覽器會使用cookie標頭自動將Cookie發送給服務器,這樣服務器就可以得到先前瀏覽器請求的相關信息了。

Servlet提供了創建,設置,讀取Cookie的API。PS:使用getCookie()讀取Cookie時,取得的是該網頁所屬域(Domain)的所有Cookie,所以返回的是Cookie數組,其中每個Cookie都以name-value的形式存在。Servlet3.0中,Cookie類新增了setHttpOnly()方法,可以將Cookie設置為僅用於HTTP,這會在set-cookie標頭上附加HttpOnly屬性,在瀏覽器支持的情況下,這些Cookie將不會被客戶端腳本比如JavaScript讀取。可以使用isHttpOnly()得知是否已被標為僅用於HTTP。

Cookie典型應用:實現用戶自動登錄。

Cookie的唯一缺點就是怕客戶端禁用了Cookie。這點可以用HttpSession搭配URL重寫解決。

HttpSession會話管理

使用req的HttpSession對象進行會話管理。

使用req.getSession()得到HttpSession對象後,最常用的就是使用setAttribute()和getAttribute()設置和獲取session屬性了。PS:這是ServletAPI中第二個可以存放屬性對象的地方。

HttpSession默認使用Cookie實現。當使用req.getHttpSession()時,Web容器會創建一個HttpSession對象,並且為這個Session對象分配一個Session ID,然後使用Cookie將這個ID存儲在瀏覽器中,在Tomcat中,這個Cookie的name是JSESSIONID,value則是這個ID值(session.getId())。值得註意的是,Web容器時只需與JVM中的一個Java程序,session對象是Web容器創建的,所以session對象也就屬於Web容器對象,不僅僅是請求範圍對象。

session對象存儲在服務端的容器中,而其ID則存在客戶端的Cookie中,每次請求來到,容器會根據發送過來的Cookie中的Session ID取得HttpSession對象,由於Session對象是容器對象,會占用內存空間,即使客戶端的Cookie失效,Session對象可能還在服務器內存中,所以不要用Session存放大型對象,必要時將大型屬性對象移除,或者不需再使用Session時,執行invalidate()讓其失效。--典型應用場景:實現註銷機制。

可以使用setMaxInactiveInteval()方法,設定瀏覽器多久沒有請求應用程序的話,HttpSession就失效,以便回收Session對象,設定單位是秒(Cookie也是秒),也可以在web.xml中設定默認的HttpSession失效時間,單位是分鐘。

Servlet3.0新增了SessionCookieConfig接口,可以使用ServletContext.getSessionCookieConfig()來取得其實例對象,更方便的同時設置Session和對應的Cookie。但是,設定SessionCookieConfig必須在ServletContext初始化之前,所以要通過配置web.xml或者實現ServletContextListener的方式來完成設定。

由於HttpSession默認使用Cookie實現,所以當用戶禁用了瀏覽器Cookie,就無法使用Cookie在瀏覽器存儲Session ID了,這時我們可以搭配URL重寫實現,即將Session ID附加在超鏈接URL後,以便下次請求將Session ID一並發送給Web服務端。我們可以使用response.encodeURL()和encodeRedirectURL()協助產生所需的URL重寫,而不必親自作判斷用戶是否禁用了Cookie和URL拼接的操作。若容器嘗試取得HttpSession實例時,能從HTTP請求中獲取到帶有Session ID的Cookie,encodeURL()則會將傳入的URL原封不動輸出,否則,encodeURL()會自動產生帶有Session ID的URL重寫。

PS:使用URL重寫,得小心保管Session ID,因為有了一個Session ID,在HttpSession存活期間,用戶在瀏覽器地址欄加上SessionID參數,如&jsessionid=xxxx,就可以取得同一個Session對象。所以在轉賬等安全操作時,一般會要求再次輸入密碼。


5 Servlet事件監聽器與過濾器

Servlet-ServletConfig-ServletContext

Web容器啟動後,會讀取Servlet配置信息(註解or部署描述文件),將Servlet類加載並實例化,並為每個Servlet設置項信息產生一個ServletConfig對象,然後調用Servlet接口的init()方法,並傳入這個ServletConfig對象。這些只在創建Servlet實例後發生一次,是在所有請求到來之前發生的,請求來之後,調用準備好的service()方法 進行服務。

ServletConfig對象作為一個Servlet設置信息的代表對象,可以從該對象中取得Servlet的初始參數等信息,以及代表整個Web應用程序的ServletContext對象。

ServletContext不是某單一Servlet的代表對象,而是整個Web應用程序加載Web容器後,容器生成的一個作為整個應用程序的代表。容器將其設置給了每個ServletConfig,而每個ServletConfig又設置給了其對應的Servlet,所以在每個Servlet中,我們可以使用this.getServletConfig().getServletContext()取得ServletContext對象。

ServletContext的幾個方法:

1)getDispatcher()

請求轉發或包含。註意傳入的路徑,以"/"開頭的稱為環境相對路徑,斜杠代表部署環境應用程序的根目錄,沒有斜杠開頭的則是請求相對路徑,實際上還是會轉化為環境相對路徑,所以統一用環境相對路徑就好。

2)getResourcePaths()

傳入環境相對路徑,來查看應用程序目錄有哪些文件和目錄,以字符串集合形式返回。

3)getResourceAsStream()

傳入環境相對路徑,返回對應的InputStream實例,接著就可以讀取文件內容。

Servlet監聽器-Listener

使用監聽器,我們可以在ServletContext對象、HttpSession對象、HttpServletRequest對象在生成、銷毀或相關屬性設置發生的時機做一些處理。

要實現監聽器,必須要繼承對應的Listener並作好配置(註解或者web.xml),Web容器才可以識別、調用這些監聽器,所有類別的監聽器都繼承自一個基礎的EventListener類。

1)ServletContext事件、監聽器

ServletContext相關的監聽器是ServletContextListener和ServletContextAttributeListener。分別為ServletContext對象的生命周期監聽器和屬性改變監聽器。

2)HttpSession事件、監聽器

HttpSession相關的監聽器稍多一些,HttpSessionListener、HttpSessionAttributeListener、HttpSessionBindingListener和HttpSessionActivationListener。分別是HttpSession對象的生命周期監聽器、屬性改變監聽器、對象綁定監聽器和對象遷移監聽器(對象在JVM間遷移)。

--HttpSessionListener應用場景:防止用戶重復登錄、統計在線人數

3)HttpServletRequest事件、監聽器

相關的監聽器是ServletRequestListener、ServletRequestAttributeListener和AsyncListener(Servlet3.0)。分別是HttpServletRequest對象的生命周期監聽器、屬性改變監聽器和異步請求處理監聽器。

PS:

生命周期監聽器和屬性改變監聽器都必須用@WebServlet註解或者在web.xml中設置,容器才會知道要加載、讀取監聽器相關設置。

在整個Web應用程序生命周期,Servlet需共享的信息可以設置為ServletContext屬性(setAttribute(),getAttribute()),除非主動移除,ServletContext屬性會一直存活於Web應用程序中,這是第三個可以設置共享信息對象的地方。

使用@WebServlet註解配置監聽器,由於該註解沒有設置初始參數的屬性,如果要設置初始參數,可以在web.xml中設置,這樣設置的屬性就是ServletContext屬性。

Servlet過濾器-Filter

在容器調用Servlet的service()方法之前,Servlet並不知道有請求的到來。

在執行Servlet的service()方法後,容器真正響應之前,瀏覽器也並不知道Servlet真正的響應是什麽。

我們可以使用過濾器攔截瀏覽器對Servlet的請求,也可以改變Servlet對瀏覽器的響應。

--應用場景:性能評測(記錄請求響應時間差)、用戶驗證(請求攔截)、字符替換、編碼設置。

要實現過濾器,必須要實現Filter接口,並使用@WebFilter註解或者在web.xml中定義過濾器,讓容器知道該加載哪些過濾器。過濾器的配置和執行與Servlet很相似,配置信息主要包括過濾器名稱、類名、URL模式。執行時先是init()方法,傳入filterConfig配置代表對象,然後執行doFilter()方法(類似Servlet.service()),最後是destroy()方法。

關鍵在於doFilter()方法,過濾器執行完init()後,進入doFilter(),doFilter()中以執行chain.doFilter()的時機分為了Servlet處理請求前、請求處理與響應和響應後三部分,chain.doFilter()會繼續接下來的過濾(如果有的話)或者Servlet。

觸發過濾器的時機,默認是瀏覽器直接發出請求,如果是通過RequestDispatcher轉發或包含的請求,則還要加配置過濾器的dispatcherType,包括DispatcherType.FORWARD、INCLUDE、REQUEST、ERROR和ASYNC(支持異步請求觸發),如果不設置,其默認值是REQUEST,即只有瀏覽器直接發出的請求才能觸發。

請求封裝器和響應封裝器

對於容器產生的HttpServletRequest對象,我們無法直接修改某些信息,而且Filter.doFIlter()方法的參數是ServletRequest、ServletResponse和FilterChain,而不是HttpServletRequest和HttpServletResponse。

當我們想要直接修改HttpServletRequest對象的請求參數值時,我們可能需要親自實現HttpServletRequest接口,讓getParameter()返回處理後的請求參數值,但是這樣又必須得實現其他所有方法,太過麻煩。

為此Servlet API提供了HttpServletRequestWrapper請求封裝器,已經幫我們實現了HttpServletRequest接口,我們只需要繼承它,然後重寫對應方法便好。

--應用場景:實現字符過濾/替換過濾器(比如過濾掉不合法字符) 和 編碼設置過濾器(不用每個Servlet設置)。

同樣的,Servlet API提供了HttpServletResponseWrapper響應封裝器,可以實現不一樣的響應。

--應用場景:對響應內容進行壓縮。

Servlet異步處理(Servlet3.0)與服務器推送技術(Server push)

。。。


6 使用JSP

Servlet中寫HTML太麻煩了,所以有了JSP。

當瀏覽器請求一個JSP,比如list.jsp,容器會將其轉譯為list_jsp.java,這其實是個HttpServlet,然後編譯為list_jsp.class,最後加載此類並實例化,所以請求JSP也就是在請求Servlet。

繼承關系:list_jsp ——>HttpBase ——> HttpServlet

學習JSP的關鍵就在於知道JSP與Servlet是一體的兩面,熟知JSP與Servlet的對象對應關系,其次才是JSP寫頁面時的各種元素的使用。

JSP的<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>,用到了JSP的指示元素,告訴容器,在將JSP轉換為Servlet時,使用UTF-8讀取.jsp轉譯為.java,然後編譯時也使用UTF-8,並設置內容類型為text/html,對應的就是Servlet的response.setContextType("text/html; charset=UTF-8");

JSP指示元素

JSP指示元素的作用就是告訴容器將JSP轉譯為Servlet源代碼時,必須遵守的信息。

指示元素語法格式為:<% 指示類型 屬性="值" ...%>

三種常見的指示類型:

1)page

page指示類型告訴容器如何轉譯目前的JSP網頁。

其三個常用的屬性是import、contextType和pageEncoding。

import屬性告訴容器轉譯JSP時,需要使用import關鍵字引入的包類。

contextType屬性告訴必須使用HttpServletResponse.setContextType();傳入的值就是contextType屬性值。

pageEncoding屬性則告知這個JSP網頁中的文字編碼。

根據contextType和pageEncoding屬性的設置,才有了response.setContextType("text/html; charset=UTF-8");

2)include

include指示類型用來告訴容器包含另一個網頁的內容進行轉譯,這就是所謂頁面包含技術。

值得註意的是,這是靜態包含,最後只會生成一個Servlet,並不是RequestDispatcher對象的include()。

3)taglib

taglib指示類型告知容器如何轉譯頁面中的標簽庫。比如以後會用到的JSTL。

JSP隱式對象

是指在JSP中可以直接使用的對象,每個隱式對象都對應一個JSP轉譯對象。

註意,隱式對象只能用在<% %>或者<%= %>之間,因為隱式對象轉譯為Servlet後,是_jspService()中的局部變量,所以無法在<%! %>中使用。

PS:

<%! 類成員聲明或類方法聲明 %>是聲明元素,其中的聲明會轉譯為Servlet的類成員和方法。

<% Java語句 %>是Scriptlet元素,其中的代碼被轉譯為Servlet的_jspService()方法中的內容。

<%= Java表達式 %>是表達式元素,其中的表達式會轉譯為Servlet的_jspService()方法中的out.print(表達式)。

九大隱式對象:

out -轉譯後對應JSPWriter對象,其內部關聯一個PrintWriter對象

request -轉譯後對應HttpServletRequest對象

response -轉譯後對應HttpServletResponse對象

config -轉譯後對應ServletConfig對象

application -轉譯後對應ServletContext對象

session -轉譯後對應HttpSession對象

pageContext -轉譯後對應PageContext對象,封裝了JSP頁面資源,並可設置頁面範圍屬性。

exception -轉譯後對應Throwable對象,代表由其它JSP頁面拋出的異常對象,只會出現於JSP錯誤頁面。

page -轉譯後對應 this,即當前Servlet實例。

其中pageContext隱式對象對應於javax.servlet.jsp.PageContext類型的對象,這個對象將所有JSP頁面的信息封裝起來,轉譯後的Servlet可通過pageContext來取得所有的JSP頁面信息,比如該JSP頁面的ServletContext、ServletConfig、HttpSession、JspWriter等對象,即所有的隱式對象都可通過pageContext來獲取。

除了封裝頁面信息外,pageContext還可以用來設置幾種範圍屬性。原本的Servlet中,可以設置屬性的對象有HttpServletRequest、HttpSession和ServletContext,可分別設置請求範圍、回話範圍和應用程序範圍屬性。

現在,在JSP中可以使用pageContext來設置和取得這些範圍屬性。不過這裏還多了一種頁面範圍屬性,表示使用範圍僅限於統一頁面。pageContext.setAttribute()有兩種,不指定範圍即默認為頁面範圍屬性。

PS:使用MVC模式後,JSP很少全面使用這些隱式對象,它的職責是顯示頁面,所以多是用來取範圍屬性值。

JSP錯誤處理

由於JSP頁面是要經過轉譯、編譯和加載為Servlet的,所以我們經常看見的JSP頁面出現的異常信息,實際上就是編譯jsp_Servlet過程中出現的異常。當然如果JSP語法沒有問題,編譯也不會出錯。

我們可以利用exception隱式對象,將異常信息單獨成頁展示,以便更好的尋找錯誤所在。

1)自定義error.jsp頁面。

只有page指示元素的isErrorPage屬性設為true的頁面才可以使用exception隱式對象。

2)web.xml部署描述文件配置錯誤頁面

一般,JSP中不會有異常處理代碼,所以碰到異常時,容器就是直接顯示異常信息和堆棧跟蹤信息。

我們可以使用配置部署描述文件,讓容器發現異常和錯誤時,自動轉發值某個URL,導向自定義的error.jsp,

然後在此頁面中使用exception隱式對象。首先是配置要轉發的頁面路徑,如果想要容器根據某種類型異常進行轉發或者基於HTTP錯誤碼轉發,則可以進一步配置。

Servlet/JSP中的JavaBean組件定義

必須實現java.io.Serializable接口;

沒有公開的類變量;

具有無參數的構造器;

具有公開的setter和getter。

JSP的EL表達式

雖然JSP提供了標準標簽(<jsp:include>與<jsp:forward>、<jsp:useBean>、<jsp:setProperty>與<jsp:getProperty>)使用,並且所有容器都支持這些標簽,使用這些JSP標準標簽可以有效的減少頁面中Scriptlet的使用(即帶<%的)。

但是標準標簽的語法過於冗長,我們可以使用語法更簡潔優雅的El表達式。

El可以處理JSP中簡單的屬性、請求參數、標頭與Cookie等信息的取得,簡單的運算和邏輯判斷,甚至可以將常用的公函編寫為EL函數,從而更有效的減少Scriptlet的出現。

詳細的EL語法可以查找網絡資料。


7 JSTL使用

不同與,JSP的標準標簽和EL表達式,JSTL並不在JSP規範中。

所以要使用JSTL,需要獲取其接口與實現jar包,加入項目的lib路徑,並在要使用JSTL的頁面中使用JSP的taglib指示元素配置要使用的標簽庫。

PS:JSTL的jar包可以在Tomcat的webapps/examples項目下的lib目錄中找到(jstl.jar standard.jar),應該也能找到Servlet-api.jar。

2018-09-18 to be continued...

Servlet/JSP代碼之上