JSP的學習(6)——九大隱式對象及其out對象


  本篇將介紹JSP中的九大隱式對象,並重點介紹其中的out對象。

  我們在之前的博客《JSP的學習(1)——基礎知識與底層原理》一文中已經知道,JSP最終要被翻譯和轉換成Servlet,在轉換后的Servlet中,由_jspService(…)方法代替我們以前直接使用Servlet中的service()方法,其實兩個功能都是一樣的,都是作為瀏覽器來訪問,然后由服務器調用的方法。

  但JSP在轉換成Servlet后,在這個Servlet中的_jspService(…)方法會自動根據我們在JSP中的一些指令,相應的生成一些對象,可供我們在JSP上直接使用這些對象,而不需要再重新使用類來創建,總共在JSP中共有直接可調用的九大隱式對象:

  1. request
  2. response
  3. page
  4. application
  5. session
  6. config
  7. exception
  8. out
  9. pageContext

  在《JSP的學習(1)——基礎知識與底層原理》一文中也已經提到,這幾個對象我們可以從Tomcat的【work】目錄下由JSP轉換后對應的.java文件可以看到在_jspService(…)方法對其中一些對象的定義:

  

我們對其中幾個對象進行快速說明:

  requestresponse 分別代表了瀏覽器發來的請求對象和服務器回送瀏覽器的響應對象,這兩個對象在之前學習Servlet時都開專門的幾篇博客講過,這里的也是一樣的用法,因此不多再做敘述。

從截圖來看:

  page對象代表了JSP轉換后的該Servlet(因為可以看截圖中定義”this”)。

  application是ServletContext的對象,因此ServletContext能怎么使用那么application在JSP中也就能怎么使用,具體請看《Servlet的學習之ServletContext(1)》。

  session對象由JSP轉換后的Servlet在_jspService(…)方法中定義和創建,注意如果在JSP頁面中使用page指令將”session”指令置為”false”,那么Servlet將不會對其進行創建。當然如果創建了session,那么功能還是同之前學習Servlet時的Session對象一樣,具體請看《Servlet的學習之Session(1)》系列。

  config對象也由JSP轉換后的Servlet在_jspService(…)方法中定義和創建,也可以按之前學習ServletConfig對象的方法作用於config對象,具體請看《Servlet的學習之ServletContext(1)》 。

  以上三個對象(application、session、config)不僅僅聲明,而是都會自動創建:

  

  exception對象我們說過,這個對象如果要想能被Servlet自動定義和創建,那么這個頁面就必須有“isErrorPage”屬性置為”true”的這個page指令(<%@  page  isErrorPage="true" %>),通常來說我們對於這個JSP都是用作錯誤處理頁面的用處。下圖為設置了“isErrorPage”屬性后Servlet自動創建exception對象:

  

  切記,一般平常沒設置isErrorPage屬性的JSP是不能直接使用exception對象的,而這對象的不容易出現,也造成很多人會忘記JSP還有這個隱式對象。

  接下來還有out對象和pageContext對象,這兩個對象因為和之前Servlet的學習中有很大的不一樣或者以前沒有這個定義,所以是我們學習JSP隱式對象中的重點。本篇先來學習JSP中的out對象,而pageContext對象將在下一篇中進行學習。

 

===============================重點對象分割線==============================

 

  out對象用於JSP向客戶端瀏覽器發送文本數據,我們之前在查看由JSP轉換后的.java文件中可以看到,Servlet正是通過out對象,將JSP中所有的模板元素(HTML標簽之類)和內容顯示都以out對象的方法將其寫入response對象中,並響應回瀏覽器:

  

  out對象是用過pageContext對象(后一篇會介紹到)的getOut()方法返回的,同時記住out對象是字符流對象,其作用和用法與ServletResponse.getWriter()方法返回的PrintWriter對象十分類似:

  

  out對象是JspWriter類的一個實例,至於JspWriter類,其實在幕后還是PrintWriter類,只是JspWriter具有緩存,只有JspWriter中的緩存進行了刷新后才創建PrintWriter類(是JspWriter幕后創建PrintWriter),並將JspWriter對象中的內容寫入到PrintWriter類對象中。

  out對象(JspWriter的實例)是具有緩存的,這個緩存是由page指令中的“buffer”屬性設置,我們在《JSP的學習(3)——語法知識二之page指令》已經學習了,“buffer”屬性默認具有8kb字節的緩存,可以設置為“none”關閉JspWriter的緩存,也可以設置“XXXkb”來重新設置JspWriter的緩存。

  使用out對象寫入了內容,只有滿足以下任一條件時,out對象才去調用ServletResponse.getWriter()方法,通過PrintWriter對象將out對象緩沖區中的內容寫到Response響應對象中:

  ① 設置page指令將“buffer”屬性為“none”

  ② out對象的buffer已滿

  ③ 整個JSP頁面執行完畢

 

關於out對象兩個示例的結論需要知道:

  1,如果在JSP頁面上同時使用out對象和response.getWriter()對象進行輸出,不管哪個對象在前面調用write()方法,都是response.getWriter()對象會先輸出數據。說到這里不妨回顧下上一篇博客《JSP的學習(5)—語法知識三之include指令》中最后一個動態包含的例子,看看訪問JSP頁面后中的源碼里面的內容。

   我們這里繼續再做一個小例子:創建一個web工程【JSPLearning】, 在index.jsp中的<body>模板元素中將內容修改如下:

1     <body>
2          <%
3             out.write("這是JspWriter對象輸出的內容");
4             response.getWriter().write("這是PrintWriter對象輸出的內容");
5           %>
6     </body>

那么在訪問JSP頁面時會發生什么情況呢?我們開啟服務器,部署該web工程,對index.jsp頁面進行訪問:

  

從訪問結果來看,並不是我們所期望的顯示順序,我們再來看看源碼:

  

  從源碼可以看到,結果正如我們開始結論所說的,使用ServletResponse.getWriter()方法獲得的PrintWriter對象輸出都會比out對象獲得的JspWriter對象輸出要早,無論兩者順序如何

  這是由於通過PrintWriter對象輸出的數據直接到Response響應對象的緩沖區中,而JspWriter通過out對象輸出由於不滿足某些條件(例如未使buffer滿或者未到JSP頁面結束),都無法幕后創建一個PrintWriter對象再將out對象緩沖區中的數據送到Response響應對象的緩沖區中。只有當JSP頁面執行結束或者使out對象的buffer滿時,才將數據再通過調用response.getWriter()獲得的PrintWriter對象將數據送到Response響應對象的緩沖區中,但此時,Response響應對象中早已經有了通過直接調用Response.getWriter().write()方法所寫入的內容

  請看如下動圖,展示了這個例子的流程:

  因此我們在JSP的編寫代碼中,應該盡量采用out對象這個隱式對象來輸出數據。

 

  2,如果我們要使用JSP來作為文件下載的方式,那么在JSP頁面中我們不能使用out對象,因為out對象是字符流,而是應該使用response.getOutputStream()方法來獲得字符流對象,並且在JSP頁面中不能出現任何由out對象輸出的數據(例如一些HTML標簽和空格之類,這些可以在轉換后的Servlet中查看是否還有)。

  我們來做一個使用JSP下載的示例,在MyEclipse中創建一個web工程【JSPLearning】,並且在【WebRoot】目錄下新建一個【download】目錄,添加進一張“1.jpg”圖片:

  

然后直接在“index.jsp”中操作好了,我們在<body>標簽中的代碼如下:

 1     <body>
 2         <%
 3             String filePath = application.getRealPath("/download/1.jpg");
 4             String fileName = filePath.substring(filePath.lastIndexOf("\\")+1);
 5         
 6             response.setHeader("content-disposition", "attachment;filename="+fileName);
 7         
 8             FileInputStream fis = new  FileInputStream(filePath);
 9             byte[] buffer = new byte[1024];
10             int len = 0;
11             while((len = fis.read(buffer))>0) {
12                 response.getOutputStream().write(buffer, 0, len);
13             }
14          %> 
15     </body>

如下圖所示:

  

  這樣就能下載了嗎,當然不行,因為這些里面的<html>、<head>、<title>等等都是由out這個字符流對象輸出的,如果這么操作,那么服務器肯定會有getOutputStream() has already been called for this response這個異常(《Servlet的學習之Response響應對象(3)》)。

  那么既然我們不能在JSP頁面中同時用到字符流和字節流的話,那么應該怎么做呢?

因為圖片不是字符,所以只能用字節流輸出,那么我們只能將JSP中所有會被out對象用write()方法寫出的數據全部刪除,比如<html>、<head>、<title>等等這些,因此我們的JSP頁面應該是成為這樣:

  

  貌似就已經把該JSP頁面中會通過out對象輸出的內容都清理完,這樣就完了嗎?嗯……還差一點,我們先來訪問下這個JSP,雖然可以下載文件,但發現還是會有getOutputStream() has already been called for this response這個異常:

  

我們不妨來看看這個“index.jsp”對應的Servlet文件:

  

  是的,還有換行,這個比較難發現,所以一般在JSP中使用字節流一定要看看對應的Servlet中到底還有沒有通過out對象輸出的數據,尤其是這些換行和空格之類的,我們再來看看修改后的樣子:

  

  不留任何換行,這一次重新來訪問該JSP,瀏覽器彈出下載窗口,服務器也沒有拋出異常,因此這樣就解決了getOutputStream() has already been called for this response這個異常。

  最后我們再來看看該“index.jsp”對應的Servlet文件源碼,我們可以看到這時候已經沒有任何通過out對象輸出的數據,包括換行。但是out對象是依然有的,通過pageContext對象的getOut方法建立。也就是說,在JSP中,雖然該Servlet通過getOut方法獲得了JspWriter這個字符流對象,但是只要沒有使用,依然可以由JSP通過響應對象再獲得字節流:

  

  於是,這篇介紹JSP中幾個隱式對象和重點學習了下out對象,那么本篇博客就該結束了,等等,最后那個下載的圖片到底是什么啊??哈哈,當然是我們可愛的銀魂一家子:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM