Java web-Request、Response、ServletConfig、ServletContext

1.Request

代表http請求的對象

1.1.繼承結構

ServletRequest – 提供一個request對象最基本的功能

|

|-- HttpServletRequest – 繼承了ServletRequest接口, 並在其基礎上添加了很多和Http協議相關的方法


1.2.request的功能

1.2.1.獲取客戶端的相關的信息

getRequestURL方法 -- 返回客戶端發出請求完整URL

如: http://localhost/day09/servlet/SecondServlet

getRequestURI方法 -- 返回請求行中的資源名部分

如: /day09/servlet/SecondServlet

getQueryString方法 -- 返回請求行中的參數部分

如: username=zhangfei&password=123

getRemoteAddr方法 -- 返回發出請求的客戶機的IP地址

如: 127.0.0.1  //可能會出現0:0:0:0:0:0:0:1形式,是ipv6的表現形式。

getMethod -- 得到客戶機請求方式

如: GET或POST

!!getContextPath -- 獲得當前web應用虛擬目錄名稱

如: /day09

注意:在寫路徑時不要將web應用的虛擬路徑的名稱寫死, 應該在需要寫web應用的名稱的地方通過getContextPath方法動態獲取

1.2.2.獲取請求頭信息

getHeader(name)方法 --- String

getHeaders(String name)方法 --- Enumeration<String>

可以通過遍歷枚舉遍歷每一個信息

例如:while (values.hasMoreElements()) {

String value = (String) values.nextElement();

System.out.println(value);

}

getHeaderNames方法 --- Enumeration<String>

getIntHeader(name)方法  --- int

getDateHeader(name)方法 --- long(日期對應毫秒)

1.2.3.獲取請求參數

getParameter(String name) --- String 通過name獲得值

getParameterValues(String name)  --- String[ ] 通過name獲得多值 checkbox

getParameterMap()  --- Map<String,String[ ]> key :name value: 多值

getParameterNames()  --- Enumeration<String> 獲得所有name

請求參數中的亂碼問題

亂碼分析: 編碼時和解碼時使用的碼錶不一致造成的!!

編碼: 是在瀏覽器進行的, 瀏覽器發送數據使用的是什麼編碼?  

瀏覽器在打開當前頁面時使用的是什麼碼錶,也會使用相同的碼錶來發送數據.

打開頁面使用utf-8, 所以發送數據也是用utf-8碼錶

解碼: 是在服務器端進行的, 服務器端接收數據使用的又是什麼編碼?

如果不指定, 服務器會使用默認的碼錶來接收瀏覽器發送過來的數據, 默認的碼錶是iso8859-1.

解決方案:

亂碼造成的原因是編碼不一致, 所以應該讓兩端的編碼保持一致!, 應該通知服務器使用utf-8來接受客戶端發送過來的數據!!

request.setCharacterEncoding(「utf-8」);//用來通知服務器使用什麼編碼來接受請求實體內容中的數據, 如果使用的是POST提交, POST提交的請求參數就是在請求實體內容中!, 所有這個方法可以解決POST提交的亂碼問題!!!

GET提交的請求參數由於不在請求實體內容中,而是在請求行中的請求資源路徑後面拼接着, 所以這行代碼對GET提交的參數亂碼不起作用!!!

GET提交的參數亂碼問題該如何解決???

GET提交的亂碼問題可以通過手動編解碼來解決!!

//>>username爲亂碼, 通過亂碼反向編碼得回二進制數組

byte[] bytes = username.getBytes("iso8859-1");

//>>通過二進制數組查詢正確的碼錶, 得出正確的數據

username = new String(bytes, "utf-8");

1.2.4.實現請求轉發

請求重定向: 302狀態碼+location響應頭

請求轉發: 和請求重定向都可以實現資源的跳轉, 但是區別是請求轉發是服務器內部的並且是同一個WEB應用內部的資源跳轉

請求轉發的特點:

一次請求對應一次響應

地址欄地址不會發生變化

請求轉發只能在同一個WEB應用內部資源之間進行跳轉! 不能是不同的WEB應用或者不同的主機!

實現代碼:

 創建servlet:RequestDemo3和RequestDemo4,RequestDemo3轉發到RequestDemo4
  在RequestDemo3中:
/*
 * 在web階段寫路徑時,除了請求轉發和請求包含在寫
 * 路徑是不用包含web應用的虛擬路徑,其他地方都需要
 * 加上web應用的虛擬路徑。
 * 完整路徑:http://localhost/day09/servlet/RequestDemo4
 */
request.getRequestDispatcher("/servlet/RequestDemo4").forward(request, response);
 
  在RequestDemo4中響應:
       response.getWriter().write("1$")

request開發細節:

在轉發之前, 如果response緩衝區被寫入了數據但是還沒有打給瀏覽器, 在轉發時response緩衝區(數據)將會被清空!

例如:在RequestDemo3中添加代碼:

response.getWriter().write("no money!");

發現瀏覽器中並未得到"no money!"這段字符串的響應。

在轉發之前, 如果response緩衝區被寫入了數據並且已經打給了瀏覽器, 轉發將會失敗!!

例如:在RequestDemo3中添加代碼:

response.getWriter().write("no money!");

response.flushBuffer();

發現瀏覽器可以顯示"no money!",因爲強制刷新了,但是轉發就會報錯,因爲已經響應過了,一次請求對應一次響應。

 

在同一個Servlet中轉發不能進行多次!!(A既轉發B, 又轉發給C)

但是可以進行多重轉發(比如A轉發給B, B再轉發給C)

1.2.5.作爲域對象來使用

域對象:如果一個對象具有一個可以被看見的範圍, 利用該對象上的map可以在整個範圍內實現資源的共享!

域對象提供的方法(可以操作map中的數據):

setAttribute(String name, Object value);  用來存儲一個對象,也可以稱之爲存儲一個域屬性

getAttribute(String name);   用來獲取request中的數據

removeAttribute(String name);   用來移除request中的域屬性

getAttributeNames();   獲取所有域屬性的名稱

 

生命週期:

一次請求開始時創建request對象, 一次請求結束時銷燬request對象

作用範圍:

整個請求鏈

主要功能:

在整個範圍內共享數據

帶數據到目的地

 

例如:在RequestDemo3中可以設置一些屬性,比如是從數據庫查詢出來的數據,轉發到RequestDemo4後,在RequestDemo4中從request域中獲取數據,並打印到頁面中。

1.2.6.實現請求包含

請求包含是服務器內部資源合併的現象


如果瀏覽器訪問Servlet A, 但是A不能獨立的處理這次請求, 需要另外一個Servlet B幫忙, 於是在A中可以將B包含進來, 包含的代碼如下:

request.getRequestDispatcher(「B的路徑」).include(request, response);

 

將B包含進來後, 將會由A和B共同來處理這次請求, 處理的結果也會合並在一起, 一起打給瀏覽器!

2.Response

2.1.Response概述

Servlet中應該如何向用戶輸出數據呢?在doGet和doPost方法的參數中,HttpServletRequest代表的是http請求,而HttServletResponse代表的是http響應。想要獲取請求中的信息時使用HttpServletRequest對象,而有數據需要發送給客戶端時,就要用到HttpServletResponse對象了。

客戶端基於HTTP協議訪問服務器時,服務器創建代表請求的Request對象和代表響應的Response對象
Request對象包含了很多操作請求相關數據的方法

Response對象包含了很多操作響應數據的方法

2.2.Response繼承結構

雖然我們經常簡稱爲response,實際上是ServletResponse接口,其中定義了很多和響應對象相關的方法,HttpServletResponse是ServletResponse接口的子接口,在ServletResponse的基礎上增加了很多和http協議相關的方法。

2.3. Response常用方法

2.3.1. 設置狀態碼

setStatus(int sc)

setStatus(int sc, String sm)

2.3.2. 設置響應頭

setIntHeader(String name, int value)

setHeader(String name, String value)

setDateHeader(String name, long date)

2.3.3. 獲取輸出流

PrintWriter getWriter()

ServletOutputStream getOutputStream();

2.4. Response輸出信息到客戶端

2.4.1. 輸出信息到客戶端api

查詢api,在Response向外輸出數據的方法有如下兩個:

PrintWriter getWriter()
ServletOutputStream getOutputStream();
其中getWriter獲取的是字符流,可以輸出字符數據到客戶端。
getOutputStream獲取的是字節流,可以輸出字節數據到客戶端。

我們現在要將字符數據發送給客戶端,可以調用getWriter方法向其中輸出數據

經測試可以正確的輸出。如圖:

接着我們測試中文。發現輸出時產生了亂碼。如圖:



2.4.2. 響應亂碼處理

這個亂碼是如何產生的呢?亂碼的產生大多是由於編碼和解碼時的碼錶不同產生的。

那麼服務器是以什麼碼錶來發送數據呢?我們發現亂碼是以「?」的形式出現的。根據我們的經驗,這種問題多半是由ISO8859-1編碼導致的。

確實是的,如果不指定,服務器默認將用iso8859-1進行編碼發送數據。瀏覽器用什麼碼錶打開呢?一般來說如果不指定,瀏覽器默認會用所在的操作系統的平臺碼,我們當前的中文系統中,默認就是使用GB2312作爲解碼碼錶的。

首先iso8859-1中沒有中文,對於無法表示的字符,iso8859-1會用「?」來替代,所以真正發送給瀏覽器的數據其實是「?」,世界上所有的碼錶都默認兼容iso8859-1,所以gb2312認識,顯示爲了「?」。如圖:

在解決這個問題時,可以通過設置response.setCharacterEncoding(「gbk」)來指定服務器發送數據時使用的碼錶。同時要注意,此行代碼必須出現在任何輸出數據的代碼之前,如果在這行代碼之前已經有任何數據寫入給了response,則此行代碼無效。

設置過後再重新測試。發現仍然是亂碼,但不再是「??」而是變成了「涓浗」。如圖:


這種類型的亂碼是怎麼發生的呢?我們接着分析。服務器用utf-8發送數據給瀏覽器,而瀏覽器用平臺碼(當前爲gbk)gbk打開自然產生了亂碼。如圖:

這種亂碼的產生是由於瀏覽器沒有使用正確的編碼打開造成的,那麼我們該如何控制瀏覽器用指定碼錶打開數據呢?

在http協議中有一個響應頭叫做Content-Type可以用來通知瀏覽器當前服務器發送的數據的格式,如果是字符格式的數據還可以指定解析時使用的碼錶。所以我們可以通過如下方法通知瀏覽器用指定碼錶打開發送的數據,代碼如下,經測試沒有亂碼。

我們通過response.setHeader("Content-Type", "text/html;charset=utf-8");通知服務器發送數據時的碼錶。

通過response.setCharacterEncoding("utf-8");通知瀏覽器解析時使用的碼錶。

兩碼相同就不會有亂碼了。

如圖:

另外response提供了setContentType()快捷方法,在它的底層,會同時做上面兩件事,所以可以一行代碼解決response產生的亂碼問題。如圖:

2.4.3. Response輸出數據時的細節

(1)getOutputStream和getWriter這兩個方法互相排斥,調用了其中的任何一個方法後,就不能再調用另一方法。

(2)Servlet程序向ServletOutputStream或PrintWriter對象中寫入的數據將被Servlet引擎從response裏面獲取,Servlet引擎將這些數據當作響應消息的正文,然後再與響應狀態行和各響應頭組合後輸出到客戶端。

(3)Serlvet的service方法結束後,Servlet引擎將檢查getWriter或getOutputStream方法返回的輸出流對象是否已經調用過close方法,如果沒有,Servlet引擎tomcat將調用close方法關閉該輸出流對象。

1.5.Response實現重定向

在HTTP協議中提供了302狀態碼和Location響應頭,通知瀏覽器收到響應後立即自動訪問Location中指定的地址,從而跳轉訪問另一個資源。

 

response.setState(302);
response.setHeader(「Location」,「。。。」);

經測試可以實現跳轉效果。

同時我們打開HTTPWatch,發現在整個重定向的過程中發生了兩次請求響應。

1.6.Response實現定時刷新

在HTTP協議中,提供了refresh響應頭,可以命令瀏覽器經過多少秒刷新頁面到哪個地址。

我們經常見到這樣的效果,當註冊成功後提示「恭喜您註冊成功。。3秒後回到主頁。。」,緊接着經過3秒回到主頁。這就是定時刷新的效果。

通過如下代碼實現。

1.7.Response實現禁止緩存

瀏覽器爲了減少對服務器的訪問,會在第一次訪問到資源後進行緩存,之後再訪問,就直接用緩存中的數據,減少對服務器的訪問。

這種緩存機制,提高了瀏覽器的響應數度,減少了服務器的訪問量,在大多數情況下是好的,但是在某些場景下可能有問題。

比如:驗證碼 實時的數據 – 火車票餘量 股票價格。。。

response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
===========================================================================

 資源的三種條轉方式:
請求轉發:
在服務器內發生的資源跳轉
一次請求一次響應,一個request對象,可以用request域在轉發時傳遞數據
瀏覽器不知道請求轉發的存在 地址欄仍然是原來的地址 刷新操作時仍然訪問的是第一個資源的地址
地址是內部使用地址 不需要寫應用名稱
request.getRequestDispatcher("/index.jsp").forward(request,response);
請求重定向:
在服務器外發生的資源跳轉
兩次請求兩次響應,兩個request對象,不可以用request域在重定向時傳遞數據
瀏覽器知道請求重定向的存在 地址欄發生變化 刷新操作時訪問的是最後一個資源地址
地址是外部使用地址  一定要寫應用名稱
response.sendRedirect("/Day10/index.jsp")


定時刷新:
在服務器外發生的資源跳轉
兩次請求兩次響應,兩個request對象,不可以用request域在重定向時傳遞數據
瀏覽器知道定時刷新的存在 地址欄發生變化 刷新操作時訪問的是最後一個資源地址
地址是外部使用地址  一定要寫應用名稱
response.setHeader("refresh","3;url=/Day10/index.jsp")


        三種跳轉方式的選擇:
        如果需要利用request域傳遞數據 --> 必須請求轉發
        如果想要在資源跳轉時改變地址欄 或 刷新操作 -->  請求重定向 或 定時刷新
        如果想在資源跳轉時,向用戶提示一些信息 --> 定時刷新

如果以上三種需求都沒有,三種方案都可以時,優先使用 請求轉發,因爲請求轉發訪問服務器的次數少,減輕服務器壓力。

============================================================================

3.ServletConfig

3.1.概述

每個Servlet都有一個ServletConfig對象 代表當前Servlet在web.xml中的配置信息

3.2.獲取ServletConfig

Servlet在初始化時候 init方法中傳入了ServletConfig對象
我們通常寫的Servlet繼承自HttpServlet - GenericServlet - Servlet
在GenericServlet中 實現了 Servlet接口定義的init方法 在其中將ServletConfig對象保存到了類的內部 比提供了 getServletConfig方法 返回該對象

所以作爲GenericServlet的子孫類 可以直接調用getServletConfig方法來獲取次對象

3.3.ServletConfig的功能

3.3.1.獲取Servlet名稱

String getServletName() 

3.3.2.獲取初始化參數

在Servlet在web.xml中的M<servlet>配置中 可以通過配置<init-param>來 配置當前Servlet的初始化參數 再在程序中通過ServletConfig對象來獲取 從而 將一些不想寫死在Servlet中的配置信息 提取到web.xml的當前Servlet的配置中
String getInitParameter(String name) 獲取當前Servlet指定名稱的初始化參數的值

Enumeration getInitParameterNames()  獲取當前Servlet所有初始化參數的名字的枚舉

4.ServletContext

4.1.概述

代表當前web應用

4.2.ServletContext生命週期

當服務器啓動時,服務器在啓動時會依次加載web應用,每一個web應用加載完成後都會創建一個ServletContext對象唯一代表該web應用,這個對象一直存活,直到web應用移除出容器或服務器關閉時,隨着應用銷燬,ServletContext對象跟着銷燬。
ServletContext一個應用只有一個,唯一的代表當前web應用,生命週期和web應用一樣,web應用創建它就創建 web應用銷燬它就跟着被銷燬。

4.3.獲取ServletContext

方法1:
ServletConfig.getServletContext();
方法2:

this.getServletContext();

4.4.ServletContext功能

4.4.1.讀取web應用初始化參數

可以在web.xml的根目錄下 通過<context-param>來爲整個web應用配置初始化參數
這些初始化參數可以通過ServletContext對象來獲取
String getInitParameter(String name) 獲取當前web應用指定名稱的初始化參數的值

Enumeration getInitParameterNames()  獲取當前web應用所有初始化參數的名字的枚舉

4.4.2.作爲域對象來使用

生命週期
和web應用命一樣長
作用範圍
整個web應用
主要功能
在整個web應用範圍內 整個web應用生命週期範圍內 共享數據

setAttribute(String name,Object obj);//向域中設置數據
Object getAttribute(String name);//從域中獲取數據
removeAttribute(String name);//從域中移除數據

4.4.3.加載web資源

(1)路徑難題

在web開發時,如果需要讀取資源文件,需要寫文件路徑
如果寫相對路徑 - 則到當前程序的啓動目錄 tomcat/bin 下找文件 找不到 報錯
如果寫絕對路徑 - 則到當前程序的啓動目錄 的根目錄 下找文件 找不到 報錯
如果寫盤符開始的絕對路徑 - 則從盤符開始找 能夠找到這個文件 但是這種方法 將路徑寫死 一旦換一個發佈環境 就無法使用了 所以不推薦

那麼路徑到底應該如何寫呢?

(2)ServletContext解決路徑難題

servletContext.getRealPath("xxxx")
此方法將會 在傳入的路徑前 拼接當前web應用的 盤符開始的絕對路徑 從而得到 指定資源的盤符開始的絕對路徑  從而訪問到資源
因爲應用的路勁格式getRealPath方法動態獲取拼接的 傳入的僅僅是資源相對於web應用根目錄的路徑 所以 並沒有將路徑寫死 所以即使換了發佈環境 路徑也仍然正確

(3)利用類加載器加載資源

在web開發時,有時無法拿到ServletContext對象,此時如何解決路徑難題呢?此時可以使用類加載器加載資源文件類加載器時虛擬機用來加載類的類此類額外提供了方法 getResource 來獲取資源的真實路徑需要注意的是,這個方法傳入的路徑必須是資源相對於類加載器默認加載類的路徑