JAVAWEB學習筆記之Servlet詳解

servlet是在服務器上運行的小程序。這個詞是在Java applet的環境中創造的,Java applet是一種當作單獨文件跟網頁一起發送的小程序,它通常用於在客戶端運行,結果得到爲用戶進行運算或者根據用戶互作用定位圖形等服務。


一、Servlet相關概念概述

1.Servlet的生命週期:通常情況下,servlet第一次被訪問的時候在內存中創建對象,在創建後立即調用init()方法進行初始化。對於每一次請求都掉用service(req,resp)方法處理請求,此時會用Request對象封裝請求信息,並用Response對象(最初是空的)代表響應消息,傳入到service方法裏供使用。當service方法處理完成後,返回服務器服務器根據Response中的信息組織稱響應消息返回給瀏覽器。響應結束後servlet並不銷燬,一直駐留在內存中等待下一次請求。直到服務器關閉或web應用被移除出虛擬主機,servlet對象銷燬並在銷燬前調用destroy()方法做一些善後的事情。

2.Servlet接口的繼承結構
Servlet接口:定義了一個servlet應該具有的方法,所有的Servlet都應該直接或間接實現此接口

GenericServlet:對Servlet接口的默認實現,通用Servlet,這是一個抽象類,其中的大部分方法都做了默認實現,只有service方法是一個抽象方法需要繼承者自己實現

HttpServlet:對HTTP協議進行了優化的Servlet,繼承自GenericServlet類,並且實現了其中的service抽象方法,默認的實現中判斷了請求的請求方式,並根據請求方式的不同分別調用不同的doXXX()方法。通常我們直接繼承HttpServlet即可



3.web.xml註冊Servlet的注意事項
3.1利用<servlet><servlet-mapping>標籤註冊一個Servlet
<servlet>
        <servlet-name>FreedomServlet</servlet-name>
        <servlet-class>com.freedom.FreedomServlet</servlet-class>  注意:此處要的是一個Servlet的完整類名,不是包含.java或.class擴展的文件路徑
    </servlet>
    <servlet-mapping>
        <servlet-name>FreedomServlet</servlet-name>
        <url-pattern>/FreedomServlet</url-pattern>
    </servlet-mapping>
    3.2一個<servlet>可以對應多個<servlet-mapping>
    3.3可以用*匹配符配置<serlvet-mapping>,但是要注意,必須是*.do或者/開頭的以/*結束的路徑。
    ~由於匹配符的引入有可能一個虛擬路徑會對應多個servlet-mapping,此時哪個最像找哪個servlet,並且*.do級別最低。
    3.4可以爲<servlet>配置<load-on-startup>子標籤,指定servlet隨着服務器的啓動而加載,其中配置的數值指定啓動的順序
    <servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
       3.5缺省servlet:如果一個servlet的對外訪問路徑被設置爲/,則該servlet就是一個缺省servlet,其他servlet不處理的請求都由它來處理
~在conf/web.xml中配置了缺省servlet,對靜態資源的訪問和錯誤頁面的輸出就是由這個缺省servlet來處理的。如果我們自己寫一個缺省servlet把爸爸web.xml中的缺省servlet覆蓋的話,會導致靜態web資源無法訪問。所以不推薦配置。
       3.6servlet的線程安全問題
       3.6.1由於通常情況下,一個servlet在內存只有一個實例處理請求,當多個請求發送過來的時候就會有多個線程操作該servlet對象,此時可能導致線程安全問題。
(1)serlvet的成員變量可能存在線程安全問題
*實驗:定義一個成員變量 int i = 0;在doXXX()方法中進行i++操作並輸出i值到客戶端,此時由於延遲可能導致線程安全問題
(2)serlvet操作資源文件時,多個線程操作同一文件引發線程安全問題
*實驗:請求帶着一個參數過來,servlet將請求參數寫入到一個文件,再讀取該文件,將讀取到的值打印到客戶端上,有可能有線程安全問題

3.6.2解決方法
(1)利用同步代碼塊解決問題。缺陷是,同一時間同步代碼塊只能處理一個請求,效率很低下,所以同步代碼塊中儘量只包含核心的導致線程安全問題的代碼。
(2)爲該servlet實現SingleThreadModel接口,此爲一個標記接口,被標記的servlet將會在內存中保存一個servlet池,如果一個線程來了而池中沒有servlet對象處理,則創建一個新的。如果池中有空閒的servlet則直接使用。這並不能真的解決線程安全問題。此接口已經被廢棄。
(3)兩種解決方案都不夠完美,所以儘量不要在servlet中出現成員變量。


4、ServletConfig
4.1.代表servlet配置的對象,可以在web.xml中<servlet>中配置
<servlet>
   <servlet-name>FreedomServlet</servlet-name>
   <servlet-class>com.freedom.FreedomServlet</servlet-class>
   <init-param>
    <param-name>data1</param-name>
    <param-value>value1</param-value>
   </init-param>
 </servlet>
 然後在servlet中利用this.getServletConfig()獲取ServletConfig對象,該對象提供了getInitParameter()和getInitParameterNames()方法,可以遍歷出配置中的配置項。
 不想在servlet中寫死的內容可以配置到此處。
  
6、ServletContext
6.1.代表當前web應用的對象。

6.2.作爲域對象使用,在不同servlet之間傳遞數據,作用範圍是整個web應用
生命週期:當web應用被加載進容器時創建代表整個web應用的ServletContext對象。當服務器關閉或web應用被移除出容器時,ServletContext對象跟着銷燬。
~域:一個域就理解爲一個框,這裏面可以放置數據,一個域既然稱作域,他就有一個可以被看見的範圍,這個範圍內都可以對這個域中的數據進行操作,那這樣的對象就叫做域對象。
6.3.在web.xml可以配置整個web應用的初始化參數,利用ServletContext去獲得
<context-param>
<param-name>param1</param-name>
<param-value>pvalue1</param-value>
</context-param>
this.getServletContext().getInitParameter("param1")
this.getServletContext().getInitParameterNames()

6.4.在不同servlet之間進行轉發
this.getServletContext().getRequestDispatcher("/servlet/FreedomServlet").forward(request, response);
方法執行結束,service就會返回到服務器,再有服務器去調用目標servlet,其中request會重新創建,並將之前的request的數據拷貝進去。
6.5.讀取資源文件
5.1由於相對路徑默認相對的是java虛擬機啓動的目錄,所以我們直接寫相對路徑將會是相對於tomcat/bin目錄,所以是拿不到資源的。如果寫成絕對路徑,當項目發佈到其他環境時,絕對路徑就錯了。
5.2爲了解決這個問題ServletContext提供了this.getServletContext().getRealPath("/1.properties"),給進一個資源的虛擬路徑,將會返回該資源在當前環境下的真實路徑。this.getServletContext().getResourceAsStream("/1.properties"),給一個資源的虛擬路徑返回到該資源真實路徑的流。
5.3當在非servlet下獲取資源文件時,就沒有ServletContext對象用了,此時只能用類加載器
classLoader.getResourceAsStream("../../1.properties"),此方法利用類加載器直接將資源加載到內存中,有更新延遲的問題,以及如果文件太大,佔用內存過大。
classLoader.getResource("../1.properties").getPath(),直接返回資源的真實路徑,沒有更新延遲的問題。


二、請求和響應(Request,Response

,Request:Request代表請求對象,其中封裝了對請求中具有請求行、請求頭、實體內容的操作的方法
1.獲取客戶機信息
getRequestURL方法返回客戶端發出請求完整URL
getRequestURI方法返回請求行中的資源名部分,在權限控制中常用
getQueryString 方法返回請求行中的參數部分
getRemoteAddr方法返回發出請求的客戶機的IP地址
getMethod得到客戶機請求方式
getContextPath 獲得當前web應用虛擬目錄名稱,特別重要!!!,工程中所有的路徑請不要寫死,其中的web應用名要以此方法去獲得。

2.獲取請求頭信息
getHeader(name)方法 --- String ,獲取指定名稱的請求頭的值
getHeaders(String name)方法 --- Enumeration<String> ,獲取指定名稱的請求頭的值的集合,因爲可能出現多個重名的請求頭
getHeaderNames方法 --- Enumeration<String> ,獲取所有請求頭名稱組成的集合
getIntHeader(name)方法  --- int ,獲取int類型的請求頭的值
getDateHeader(name)方法 --- long(日期對應毫秒) ,獲取一個日期型的請求頭的值,返回的是一個long值,從1970年1月1日0時開始的毫秒值

通過referer信息防盜鏈
String ref = request.getHeader("Referer");
if (ref == null || ref == "" || !ref.startsWith("http://localhost")) {
response.sendRedirect(request.getContextPath() + "/homePage.html");
} else {
this.getServletContext().getRequestDispatcher("/WEB-INF/fengjie.html").forward(request, response);
}
3.獲取請求參數
getParameter(name) --- String 通過name獲得值
getParameterValues(name)  --- String[ ] 通過name獲得多值 checkbox
getParameterNames  --- Enumeration<String> 獲得所有請求參數名稱組成的枚舉
getParameterMap  --- Map<String,String[ ]> 獲取所有請求參數的組成的Map集合,注意,其中的鍵爲String,值爲String[]

獲取請求參數時亂碼問題:
瀏覽器發送的請求參數使用什麼編碼呢?當初瀏覽器打開網頁時使用什麼編碼,發送就用什麼編碼。
服務器端獲取到發過來的請求參數默認使用ISO8859-1進行解碼操作,中文一定有亂碼問題
對於Post方式提交的數據,可以設置request.setCharacterEncoding("gb2312");來明確指定獲取請求參數時使用編碼。但是此種方式只對Post方式提交有效。
對於Get方式提交的數據,就只能手動解決亂碼:String newName = new String(name.getBytes("ISO8859-1"),"gb2312");此種方法對Post方式同樣有效。
在tomcat的server.xml中可以配置http連接器的URIEncoding可以指定服務器在獲取請求參數時默認使用的編碼,從而一勞永逸的決絕獲取請求參數時的亂碼問題。也可以指定useBodyEncodingForURI參數,令request.setCharacterEncoding也對GET方式的請求起作用,但是這倆屬性都不推薦使用,因爲發佈環境往往不允許修改此屬性。


4.利用請求域傳遞對象
生命週期:在service方法調用之前由服務器創建,傳入service方法。整個請求結束,request生命結束。
作用範圍:整個請求鏈。
作用:在整個請求鏈中共享數據,最常用的:在Servlet中處理好的數據要交給Jsp顯示,此時參數就可以放置在Request域中帶過去。

5.request實現請求轉發
ServletContext可以實現請求轉發,request也可以。
  this.getServletContext().getRequestDispatcher("").forward(request,response);
          request.getRequestDispatcher("").forward(request,response);
在forward之前輸入到response緩衝區中的數據,如果已經被髮送到了客戶端,forward將失敗,拋出異常
在forward之前輸入到response緩衝區中的數據,但是還沒有發送到客戶端,forward可以執行,但是緩衝區將被清空,之前的數據丟失。注意丟失的只是請求體中的內容,頭內容仍然有效。
在一個Servlet中進行多次forward也是不行的,因爲第一次forward結束,response已經被提交了,沒有機會再forward了
總之,一條原則,一次請求只能有一次響應,響應提交走後,就再沒有機會輸出數據給瀏覽器了。

6.RequestDispatcher進行include操作
forward沒有辦法將多個servlet的輸出組成一個輸出,因此RequestDispatcher提供了include方法,可以將多個Servlet的輸出組成一個輸出返回個瀏覽器
request.getRequestDispatcher("/servlet/Demo17Servlet").include(request, response);
response.getWriter().write("from Demo16");
request.getRequestDispatcher("/servlet/Demo18Servlet").include(request, response);
常用在頁面的固定部分單獨寫入一個文件,在多個頁面中include進來簡化代碼量。


Response
1.Resonse的繼承結構:ServletResponse--HttpServletResponse
2.Response代表響應,於是響應消息中的 狀態碼、響應頭、實體內容都可以由它進行操作
3.利用Response輸出數據到客戶端
response.getOutputStream().write("中文".getBytes())輸出數據,這是一個字節流,是什麼字節輸出什麼字節,而瀏覽器默認用平臺字節碼打開服務器發送的數據,如果服務器端使用了非平臺碼去輸出字符的字節數據就需要明確的指定瀏覽器編碼時所用的碼錶,以防止亂碼問題。response.addHeader("Content-type","text/html;charset=gb2312")
response.getWriter().write(“中文”);輸出數據,這是一個字符流,response會將此字符進行轉碼操作後輸出到瀏覽器,這個過程默認使用ISO8859-1碼錶,而ISO8859-1中沒有中文,於是轉碼過程中用?代替了中文,導致亂碼問題。可以指定response在轉碼過程中使用的目標碼錶,防止亂碼。response.setCharcterEncoding("gb2312");
其實response還提供了setContentType("text/html;charset=gb2312")方法,此方法會設置content-type響應頭,通知瀏覽器打開的碼錶,同時設置response的轉碼用碼錶,從而一行代碼解決亂碼。
4.利用Response 設置 content-disposition頭實現文件下載
設置響應頭content-disposition爲“attachment;filename=xxx.xxx”
利用流將文件讀取進來,再利用Response獲取響應流輸出
如果文件名爲中,一定要進行URL編碼(URLEncoder),編碼所用的碼錶一定要是utf-8
5.refresh頭控制定時刷新
設置響應頭Refresh爲一個數值,指定多少秒後刷新當前頁面
設置響應頭Refresh爲 3;url=/Day05/index.jsp,指定多少秒後刷新到哪個頁面
可以用來實現註冊後“註冊成功,3秒後跳轉到主頁”的功能
在HTML可以利用<meta http-equiv= "" content="">標籤模擬響應頭的功能。
6.利用response設置expires、Cache-Control、Pragma實現瀏覽器是否緩存資源,這三個頭都可以實現,但是由於歷史原因,不同瀏覽器實現不同,所以一般配合這三個頭使用
6.1控制瀏覽器不要緩存(驗證碼圖片不緩存)設置expires爲0或-1設置Cache-Control爲no-cache、Pragma爲no-cache
6.2控制瀏覽器緩存資源。即使不明確指定瀏覽器也會緩存資源,這種緩存沒有截至日期。當在地址欄重新輸入地址時會用緩存,但是當刷新或重新開瀏覽器訪問時會重新獲得資源。 response.setDataHeader(expires,時間值)
如果明確指定緩存時間,瀏覽器緩存是,會有一個截至日期,在截至日期到期之前,當在地址欄重新輸入地址或重新開瀏覽器訪問時都會用緩存,而當刷新時會重新獲得資源。
7.Response實現請求重定向
7.1古老方法:response.setStatus(302);response.addHeader("Location","URL");
7.2快捷方式:response.sendRedirect("URL");

8.Response注意的內容:
9.1對於一次請求,Response的getOutputStream方法和getWriter方法是互斥,只能調用其一,特別注意forward後也不要違反這一規則。
9.2利用Response輸出數據的時候,並不是直接將數據寫給瀏覽器,而是寫到了Response的緩衝區中,等到整個service方法返回後,由服務器拿出response中的信息組成HTTP響應消息返回給瀏覽器。
9.3service方法返回後,服務器會自己檢查Response獲取的OutputStream或者Writer是否關閉,如果沒有關閉,服務器自動幫你關閉,一般情況下不要自己關閉這兩個流。


請求重定向和請求轉發的區別 
1.區別
RequestDispatcher.forward方法只能將請求轉發給同一個WEB應用中的組件;而HttpServletResponse.sendRedirect 方法還可以重定向到同一個站點上的其他應用程序中的資源,甚至是使用絕對URL重定向到其他站點的資源。 
如果傳遞給HttpServletResponse.sendRedirect 方法的相對URL以“/”開頭,它是相對於服務器的根目錄;如果創建RequestDispatcher對象時指定的相對URL以“/”開頭,它是相對於當前WEB應用程序的根目錄。 
調用HttpServletResponse.sendRedirect方法重定向的訪問過程結束後,瀏覽器地址欄中顯示的URL會發生改變,由初始的URL地址變成重定向的目標URL;調用RequestDispatcher.forward 方法的請求轉發過程結束後,瀏覽器地址欄保持初始的URL地址不變。
HttpServletResponse.sendRedirect方法對瀏覽器的請求直接作出響應,響應的結果就是告訴瀏覽器去重新發出對另外一個URL的訪問請求;RequestDispatcher.forward方法在服務器端內部將請求轉發給另外一個資源,瀏覽器只知道發出了請求並得到了響應結果,並不知道在服務器程序內部發生了轉發行爲。 
RequestDispatcher.forward方法的調用者與被調用者之間共享相同的request對象和response對象,它們屬於同一個訪問請求和響應過程;而HttpServletResponse.sendRedirect方法調用者與被調用者使用各自的request對象和response對象,它們屬於兩個獨立的訪問請求和響應過程。 


URL編碼
1.由於HTTP協議規定URL路徑中只能存在ASCII碼中的字符,所以如果URL中存在中文或特殊字符需要進行URL編碼。
2.編碼原理:
將空格轉換爲加號(+) 
對0-9,a-z,A-Z之間的字符保持不變 
對於所有其他的字符,用這個字符的當前字符集編碼在內存中的十六進制格式表示,並在每個字節前加上一個百分號(%)。如字符“+”用%2B表示,字符“=”用%3D表示,字符“&”用%26表示,每個中文字符在內存中佔兩個字節,字符“中”用%D6%D0表示,字符“國”用%B9%FA表示調對於空格也可以直接使用其十六進制編碼方式,即用%20表示,而不是將它轉換成加號(+) 
說明:
如果確信URL串的特殊字符沒有引起使用上的岐義或衝突你也可以對這些字符不進行編碼,而是直接傳遞給服務器。例如,http://www.it315.org/dealregister.html?name=中國&password=123 
如果URL串中的特殊字符可能會產生岐義或衝突,則必須對這些特殊字符進行URL編碼。例如,服務器會將不編碼的“中+國”當作“中國”處理。還例如,當name參數值爲“中&國”時,如果不對其中的“&”編碼,URL字符串將有如下形式:http://www.it315.org/dealregister.html?name=中&國&password=123,應編碼爲:http://www.it315.org/dealregister.html?name=中%26國&password=123 
http://www.it315.org/example/index.html#section2可改寫成http://www.it315.org/example%2Findex.html%23section2 
3.在java中進行URL編碼和解碼
URLencoder.encode("xxxx","utf-8");
URLDecoder.decode(str,"utf-8");




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章