一、Servlet概述
1.sun公司提供的動態web資源開發技術。本質是上一段java小程序,要求這個小程序必須實現Servlet接口,以便服務器能夠調用。
2.開發Servlet的兩個步驟
*實驗:Servlet的快速入門
(1)步驟一:寫一個java程序實現Servlet接口(此處直接繼承了默認實現類GenericServlet)
1 package cn.itheima; 2 import java.io.*; 3 import javax.servlet.*; 4 5 public class FirstServlet extends GenericServlet{ 6 public void service(ServletRequest req, ServletResponse res)throws ServletException, java.io.IOException{ 7 res.getOutputStream().write("My FirstServlet!".getBytes()); 8 } 9 10 }
(2)將編譯好的帶包的.class放到WEB-INF/classes下以外,還要配置web應用的 web.xml注冊Servlet
1 <servlet> 2 <servlet-name>FirstServlet</servlet-name> 3 <servlet-class>cn.itheima.FirstServlet</servlet-class> 4 </servlet> 5 <servlet-mapping> 6 <servlet-name>FirstServlet</servlet-name> 7 <url-pattern>/FirstServlet</url-pattern> 8 </servlet-mapping>
二、Servlet的詳述
1.生命周期:一件事物什么時候生,什么時候死,在生存期間必然會做的事情,所有這些放在一起就是該事物的聲明周期。
2.Servlet的調用過程/生命周期:通常情況下,servlet第一次被訪問的時候在內存中創建對象,在創建后立即調用init()方法進行初始化。對於每一次請求都掉用service(req,resp)方法處理請求,此時會用Request對象封裝請求信息,並用Response對象(最初是空的)代表響應消息,傳入到service方法里供使用。當service方法處理完成后,返回服務器服務器根據Response中的信息組織稱響應消息返回給瀏覽器。響應結束后servlet並不銷毀,一直駐留在內存中等待下一次請求。直到服務器關閉或web應用被移除出虛擬主機,servlet對象銷毀並在銷毀前調用destroy()方法做一些善后的事情。
3.Servlet接口的繼承結構
Servlet接口:定義了一個servlet應該具有的方法,所有的Servlet都應該直接或間接實現此接口
|
|----GenericServlet:對Servlet接口的默認實現,通用Servlet,這是一個抽象類,其中的大部分方法都做了默認實現,只有service方法是一個抽象方法需要繼承者自己實現 |
|----HttpServlet:對HTTP協議進行了優化的Servlet,繼承自GenericServlet類,並且實現了其中的service抽象方法,默認的實現中判斷了請求的請求方式,並根據請求方式的不同分別調用不同的doXXX()方法。通常我們直接繼承HttpServlet即可
4.web.xml注冊Servlet的注意事項
4.1利用<servlet><servlet-mapping>標簽注冊一個Servlet
1 <servlet> 2 <servlet-name>FirstServlet</servlet-name> 3 <servlet-class>cn.itheima.FirstServlet</servlet-class> 4 </servlet> 5 <servlet-mapping> 6 <servlet-name>FirstServlet</servlet-name> 7 <url-pattern>/FirstServlet</url-pattern> 8 </servlet-mapping>
4.2一個<servlet>可以對應多個<servlet-mapping>
4.3可以用*匹配符配置<serlvet-mapping>,但是要注意,必須是*.do或者/開頭的以/*結束的路徑。
由於匹配符的引入有可能一個虛擬路徑會對應多個servlet-mapping,此時哪個最像找哪個servlet,並且*.do級別最低。
4.4可以為<servlet>配置<load-on-startup>子標簽,指定servlet隨着服務器的啟動而加載,其中配置的數值指定啟動的順序
1 <servlet> 2 <servlet-name>invoker</servlet-name> 3 <servlet-class>org.apache.catalina.servlets.InvokerServlet</servlet-class> 4 <load-on-startup>2</load-on-startup> 5 </servlet>
4.5缺省servlet:如果一個servlet的對外訪問路徑被設置為/,則該servlet就是一個缺省servlet,其他servlet不處理的請求都由它來處理 ~在conf/web.xml中配置了缺省servlet,對靜態資源的訪問和錯誤頁面的輸出就是由這個缺省servlet來處理的。如果我們自己寫一個缺省servlet把爸爸web.xml中的缺省servlet覆蓋的話,會導致靜態web資源無法訪問。所以不推薦配置。
4.6servlet的線程安全問題
4.6.1由於通常情況下,一個servlet在內存只有一個實例處理請求,當多個請求發送過來的時候就會有多個線程操作該servlet對象,此時可能導致線程安全問題。
(1)serlvet的成員變量可能存在線程安全問題
*實驗:定義一個成員變量 int i = 0;在doXXX()方法中進行i++操作並輸出i值到客戶端,此時由於延遲可能導致線程安全問題
(2)serlvet操作資源文件時,多個線程操作同一文件引發線程安全問題
*實驗:請求帶着一個參數過來,servlet將請求參數寫入到一個文件,再讀取該文件,將讀取到的值打印到客戶端上,有可能有線程安全問題
4.6.2解決方法
(1)利用同步代碼塊解決問題。缺陷是,同一時間同步代碼塊只能處理一個 請求,效率很低下,所以同步代碼塊中盡量只包含核心的導致線程安全問題的代碼。
(2)為該servlet實現SingleThreadModel接口,此為一個標記接口,被標記的servlet將會在內存中保存一個servlet池,如果一個線程來了而池中沒有servlet對象處理,則創建一個新的。如果池中有空閑的servlet則直接使用。這並不能真的解決線程安全問題。此接口已經被廢棄。
(3)兩種解決方案都不夠完美,所以盡量不要在servlet中出現成員變量。
三、ServletConfig -- 代表當前Servlet在web.xml中的配置信息
1.方法
String getServletName() -- 獲取當前Servlet在web.xml中配置的名字
String getInitParameter(String name) -- 獲取當前Servlet指定名稱的初始化參數的值
Enumeration getInitParameterNames() -- 獲取當前Servlet所有初始化參數的名字組成的枚舉
ServletContext getServletContext() -- 獲取代表當前web應用的ServletContext對象
2.代表servlet配置的對象,可以在web.xml中<servlet>中配置
1 <servlet> 2 <servlet-name>Demo5Servlet</servlet-name> 3 <servlet-class>cn.itheima.Demo5Servlet</servlet-class> 4 <init-param> 5 <param-name>data1</param-name> 6 <param-value>value1</param-value> 7 </init-param> 8 </servlet>
然后在servlet中利用this.getServletConfig()獲取ServletConfig對象,該對象提供了getInitParameter()和getInitParameterNames()方法,可以遍歷出配置中的配置項。不想在servlet中寫死的內容可以配置到此處。
四、ServletContext -- 代表當前web應用
1.代表當前web應用的對象。
2.做為域對象可以在整個web應用范圍內共享數據
域對象:在一個可以被看見的范圍內共享數據用到對象
作用范圍:整個web應用范圍內共享數據
生命周期:當web應用被加載進容器時創建代表整個web應用的ServletContext對象,當服務器關閉或web應用被移除出容器時,ServletContext對象跟着銷毀。
void setAttribute(String,Object); Object getAttribute(String); void removeAttribute(String);
*域:一個域就理解為一個框,這里面可以放置數據,一個域既然稱作域,他就有一個可以被看見的范圍,這個范圍內都可以對這個域中的數據進行操作,那這樣的對象就叫做域對象。
3.在web.xml可以配置整個web應用的初始化參數,利用ServletContext去獲得
請求參數 parameter --- 瀏覽器發送過來的請求中的參數信息
初始化參數 initparameter --- 在web.xml中為Servlet或ServletContext配置的初始化時帶有的基本參數
域屬性 attribute --- 四大作用域中存取的鍵值對
1 <context-param> 2 <param-name>param1</param-name> 3 <param-value>pvalue1</param-value> 4 </context-param> 5 this.getServletContext().getInitParameter("param1") 6 this.getServletContext().getInitParameterNames()
4.實現Servlet的轉發
重定向 : 302+Location
請求轉發 : 服務器內不進行資源流轉
*請求轉發是一次請求一次響應實現資源流轉.請求重定向兩次請求兩次響應.
this.getServletContext().getRequestDispatcher("/servlet/Demo10Servlet").forward(request, response); 方法執行結束,service就會返回到服務器,再有服務器去調用目標servlet,其中request會重新創建,並將之前的request的數據拷貝進去。
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(),直接返回資源的真實路徑,沒有更新延遲的問題。
五、Response
1.Resonse的繼承結構: ServletResponse--HttpServletResponse
2.Response代表響應,於是響應消息中的 狀態碼、響應頭、實體內容都可以由它進行操作,由此引伸出如下實驗:
3.利用Response輸出數據到客戶端:
response.getOutputStream().write("中文".getBytes())輸出數據,這是一個字節流,是什么字節輸出什么字節, 而瀏覽器默認用平台字節碼打開服務器發送的數據,如果服務器端使用了非平台碼去輸出字符的字節數據就需要明確的指定瀏覽器編碼時所用的碼表, 以防止亂碼問題。
response.addHeader("Content-type","text/html;charset=gb2312")
例: response.setContentTye("text/html;charset=utf-8");
response.getOutputStream().write("".getBytes("utf-8"));
response.getWriter().write(“中文”);輸出數據,這是一個字符流,response會將此字符進行轉碼操作后輸出到瀏覽器,這個過程默認使用ISO8859-1碼表,而ISO8859-1中沒有中文,於是轉碼過程中用?代替了中文,導致亂碼問題。可以指定response在轉碼過程中使用的目標碼表,防止亂碼。
response.setCharcterEncoding("gb2312");
例: response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("");
***其實response還提供了setContentType("text/html;charset=gb2312")方法,此方法會設置content-type響應頭,通知瀏覽器打開的碼表, 同時設置response的轉碼用碼表,從而一行代碼解決亂碼。
4.利用Response 設置 content-disposition頭實現文件下載: 設置響應頭content-disposition為“attachment;filename=xxx.xxx” 利用流將文件讀取進來,再利用Response獲取響應流輸出。如果文件名為中文,一定要進行URL編碼,編碼所用的碼表一定要是utf-8
例1: response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode("美女.jpg", "utf-8"));
5.refresh頭控制定時刷新: 設置響應頭Refresh為一個數值,指定多少秒后刷新當前頁面 設置響應頭Refresh為 3;url=/Day05/index.jsp,指定多少秒后刷新到哪個頁面 可以用來實現注冊后“注冊成功,3秒后跳轉到主頁”的功能 在HTML可以利用<meta http-equiv= "" content="">標簽模擬響應頭的功能。
例1:response.setHeader("refresh", "3;url=/Day04/index.jsp");
6.控制是否緩存資源: 利用response設置expires、Cache-Control、Pragma實現瀏覽器是否緩存資源,這三個頭都可以實現,但是由於歷史原因,不同瀏覽器實現不同,所以一般配合這三個頭使用
6.1控制瀏覽器不要緩存(驗證碼圖片不緩存)設置expires為0或-1設置Cache-Control為no-cache、Pragma為no-cache
6.2控制瀏覽器緩存資源。即使不明確指定瀏覽器也會緩存資源,這種緩存沒有截至日期。當在地址欄重新輸入地址時會用緩存,但是當刷新或重新開瀏覽器訪問時會重新獲得資源。如果明確指定緩存時間,瀏覽器緩存是,會有一個截至日期,在截至日期到期之前,當在地址欄重新輸入地址或重新開瀏覽器訪問時都會用緩存,而當刷新時會重新獲得資源。
例:response.setDateHeader("Expires", System.currentTimeMillis()+1000l*3600*24*30);
例2: response.setIntHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
7.Response實現請求重定向
7.1古老方法:response.setStatus(302);response.addHeader("Location","URL");
7.2快捷方式:response.sendRedirect("URL");
*8.輸出驗證碼圖片
1 public class ValiImg extends HttpServlet { 2 3 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 4 response.setDateHeader("Expires", -1); 5 response.setHeader("Cache-Control", "no-cache"); 6 response.setHeader("Pragma", "no-cache"); 7 // 1.在內存中構建出一張圖片 8 int height = 30; 9 int width = 120; 10 int xpyl = 5; 11 int ypyl = 22; 12 int bang = 20; 13 BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 14 // 2.獲取圖像上的畫布 15 Graphics2D g = (Graphics2D) img.getGraphics(); 16 // 3.設置背景色 17 g.setColor(Color.LIGHT_GRAY); 18 g.fillRect(0, 0, width, height); 19 // 4.設置邊框 20 g.setColor(Color.BLUE); 21 g.drawRect(0, 0, width - 1, height - 1); 22 // 5.畫干擾線 23 for (int i = 0; i < 5; i++) { 24 g.setColor(Color.RED); 25 g.drawLine(randNum(0, width), randNum(0, height), randNum(0, width), randNum(0, height)); 26 } 27 // 6.寫字 28 String base = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; 29 for (int i = 0; i < 4; i++) { 30 g.setColor(new Color(randNum(0, 255), randNum(0, 255), randNum(0, 255))); 31 g.setFont(new Font("黑體", Font.BOLD, bang)); 32 int r = randNum(-45, 45); 33 g.rotate(1.0 * r / 180 * Math.PI, xpyl + (i * 30), ypyl); 34 g.drawString(base.charAt(randNum(0, base.length() - 1)) + "", xpyl + (i * 30), ypyl); 35 g.rotate(1.0 * -r / 180 * Math.PI, xpyl + (i * 30), ypyl); 36 } 37 // 將圖片輸出到瀏覽器 38 ImageIO.write(img, "jpg", response.getOutputStream()); 39 } 40 41 private Random rand = new Random(); 42 43 private int randNum(int begin, int end) { 44 return rand.nextInt(end - begin) + begin; 45 } 46 47 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 48 doGet(request, response); 49 } 50 51 }
9.Response注意的內容:
9.1對於一次請求,Response的getOutputStream方法和getWriter方法是互斥,只能調用其一,特別注意forward后也不要違反這一規則。
9.2利用Response輸出數據的時候,並不是直接將數據寫給瀏覽器,而是寫到了Response的緩沖區中,等到整個service方法返回后,由服務器拿出response中的信息組成HTTP響應消息返回給瀏覽器。
9.3service方法返回后,服務器會自己檢查Response獲取的OutputStream或者Writer是否關閉,如果沒有關閉,服務器自動幫你關閉,一般情況下不要自己關閉這兩個流。
六、Request:Request代表請求對象,其中封裝了對請求中具有請求行、請求頭、實體內容的操作的方法
ServletRequest -- 通用request,提供一個request應該具有的最基本的方法
|
|--HttpServletRequest -- ServletRequest的孩子,針對http協議進行了進一步的增強
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信息防盜鏈
1 String ref = request.getHeader("Referer"); 2 if (ref == null || ref == "" || !ref.startsWith("http://localhost")) { 3 response.sendRedirect(request.getContextPath() + "/homePage.html"); 4 } else { 5 this.getServletContext().getRequestDispatcher("/WEB-INF/fengjie.html").forward(request, response); 6 }
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.利用請求域傳遞對象 request對象同時也是一個域對象,開發人員通過request對象在實現轉發時,把數據通過request對象帶給其它web資源處理 方法:setAttribute方法 getAttribute方法 removeAttribute方法 getAttributeNames方法
生命周期:在service方法調用之前由服務器創建,傳入service方法。整個請求結束,request生命結束。
作用范圍:整個請求鏈。
作用:在整個請求鏈中共享數據,最常用的:在Servlet中處理好的數據要交給Jsp顯示,此時參數就可以放置在Request域中帶過去。
5.request實現請求轉發 ServletContext可以實現請求轉發,request也可以。
在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進來簡化代碼量。
七、會話技術
1.會話技術:從瀏覽器開始訪問服務器,到關閉瀏覽器,這期間發生了許多次請求和響應,這個過程就叫做一次會話。
2.問題:如何在一次會話中保存會話相關的數據。
3.Cookie:將會話相關的數據保存到瀏覽器中,並且在每次訪問服務器時都帶過去。
3.1javax.servlet.http.Cookie,可以直接利用此類的構造方法創建一個Cookie,創建出來的Cookie需要設置一個名稱和值
3.2response身上具有addCookie的方法,可以將創建出來的組織成響應消息中的set-cookie頭,通知瀏覽器保存該cookie
3.3request身上具有getCookies方法,可以獲取瀏覽器帶過來的所有Cookie
3.4Cookie方法:注意,瀏覽器是根據cookie的名稱加上cookie的path來區分是否是同一個cookie的,如果需要覆蓋之前的cookie,除了保證名稱相同外還要保證path也相同。
public Cookie(String name,String value)利用構造方法創建一個Cookie對象,在創建的時候就要指定該Cookie的名和值
setValue與getValue方法 設置或者獲取Cookie的值
setMaxAge與getMaxAge方法 如果不設置cookie的MaxAge(或將其值設置為負值),則默認情況下瀏覽器會將cookie保存在瀏覽器的內存中,會隨着瀏覽器關閉而消失。如果設置為一個正值,則代表該Cookie要保存的以秒為單位的時間值,如此,該cookie將會被瀏覽器保存到硬盤中去。如果將MaxAge設置為0,則是通知瀏覽器去刪除該Cookie。
setPath與getPath方法 用來指定訪問哪個ULR及其子URL時帶上此cookie,如果不設置此值,則瀏覽器默認會將發送該cookie的servlet所在的路徑作為path使用。
例如:
setPath("/Day06")則/Day06/.../...的路徑都會帶上該Cookie
如果發送該Cookie的Servlet是 /Day06/servlet/Demo1Servlet,並且未設置setPat,則瀏覽器在訪問/Day06/servlet/..時會帶上該cookie
setDomain與getDomain方法 設置cookie對應的域名,此方法一旦調用,則瀏覽器會認為該cookie是一個第三方cookie而拒收
getName方法 獲取該cookie的名字,注意沒有setName方法,一個Cookie一旦創建出來就不能修改名字了
3.5瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。
示例: cookie顯示最近瀏覽的商品

1 Book.java 2 public class Book implements Serializable { 3 private String id; 4 private String name; 5 private String price; 6 private String auth; 7 private String publish; 8 private String description; 9 10 public Book() { 11 } 12 13 public Book(String id, String name, String price, String auth, 14 String publish, String description) { 15 super(); 16 this.id = id; 17 this.name = name; 18 this.price = price; 19 this.auth = auth; 20 this.publish = publish; 21 this.description = description; 22 } 23 24 public String getDescription() { 25 return description; 26 } 27 28 public void setDescription(String description) { 29 this.description = description; 30 } 31 32 public String getId() { 33 return id; 34 } 35 public void setId(String id) { 36 this.id = id; 37 } 38 public String getName() { 39 return name; 40 } 41 public void setName(String name) { 42 this.name = name; 43 } 44 public String getPrice() { 45 return price; 46 } 47 public void setPrice(String price) { 48 this.price = price; 49 } 50 public String getAuth() { 51 return auth; 52 } 53 public void setAuth(String auth) { 54 this.auth = auth; 55 } 56 public String getPublish() { 57 return publish; 58 } 59 public void setPublish(String publish) { 60 this.publish = publish; 61 } 62 63 } 64 65 BookDao.java 66 public class BookDao { 67 private static Map<String,Book> bookMap = new LinkedHashMap<String, Book>(); 68 69 private BookDao() { 70 } 71 static{ 72 bookMap.put("1", new Book("1","三國演義","99.0","朴乾","黑馬出版社","一群男人糾結不清的故事....")); 73 bookMap.put("2", new Book("2","西游記","10.0","曹睿","傳智出版社","一個和尚一個猴子一頭豬和一個禿子去西天的故事...")); 74 bookMap.put("3", new Book("3","水滸傳","2.0","奧巴馬","人民教育出版社","105個男人和3個女人闖盪江湖的故事")); 75 bookMap.put("4", new Book("4","哈利波特","200.0","哈利波特","科技出版社","混亂不堪的故事...")); 76 } 77 78 public static Map<String,Book> getBooks(){ 79 return bookMap; 80 } 81 82 public static Book getBook(String id){ 83 return bookMap.get(id); 84 } 85 } 86 87 BookListServlet.java 88 public class BookListServlet extends HttpServlet { 89 90 /** 91 * 92 */ 93 private static final long serialVersionUID = -1270524418156452134L; 94 95 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 96 97 response.setContentType("text/html;charset=utf-8"); 98 99 // 1.查詢數據庫中所有的書展示 100 Map<String, Book> map = BookDao.getBooks(); 101 for (Map.Entry<String, Book> entry : map.entrySet()) { 102 Book book = entry.getValue(); 103 response.getWriter().write("<a href='" + request.getContextPath() + "/servlet/BookInfoServlet?id=" 104 + book.getId() + "'>" + book.getName() + "</a><br>"); 105 } 106 response.getWriter().write("<hr>"); 107 108 // 2.顯示之前看過的書 109 Cookie[] cs = request.getCookies(); 110 Cookie findC = null; 111 if (cs != null) { 112 for (Cookie c : cs) { 113 if ("last".equals(c.getName())) { 114 findC = c; 115 } 116 } 117 } 118 if (findC == null) { 119 response.getWriter().write("沒有看過任何書!"); 120 } else { 121 response.getWriter().write("您曾經瀏覽過的書:<br>"); 122 String[] ids = findC.getValue().split(","); 123 for (String id : ids) { 124 Book book = BookDao.getBook(id); 125 response.getWriter().write(book.getName() + "<br>"); 126 } 127 } 128 } 129 130 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 131 doGet(request, response); 132 } 133 134 } 135 136 BookInfoServlet.java 137 public class BookInfoServlet extends HttpServlet { 138 /** 139 * 140 */ 141 private static final long serialVersionUID = -4530178673682834862L; 142 143 public void doGet(HttpServletRequest request, HttpServletResponse response) 144 throws ServletException, IOException { 145 response.setContentType("text/html;charset=utf-8"); 146 //1.獲取要看的書的id,查詢數據庫找出書,輸出書的詳細信息 147 String id = request.getParameter("id"); 148 Book book = BookDao.getBook(id); 149 if(book==null){ 150 response.getWriter().write("找不到這本書!"); 151 return; 152 }else{ 153 response.getWriter().write("<h1>書名:"+book.getName()+"</h1>"); 154 response.getWriter().write("<h3>作者:"+book.getAuth()+"</h3>"); 155 response.getWriter().write("<h3>售價:"+book.getPrice()+"</h3>"); 156 response.getWriter().write("<h3>出版社:"+book.getPublish()+"</h3>"); 157 response.getWriter().write("<h3>描述信息:"+book.getDescription()+"</h3>"); 158 } 159 160 //2.發送cookie保存最后看過的書 161 // --- 1 --> 1 162 // 1 --2,1 --> 2,1 163 // 2,1--3,2,1 --> 3,2,1 164 // 3,2,1 -- 4,3,2 --> 4,3,2 165 // 4,3,2 --3,4,2 --> 3,4,2 166 String ids = ""; 167 168 Cookie [] cs = request.getCookies(); 169 Cookie findC = null; 170 if(cs!=null){ 171 for(Cookie c : cs){ 172 if("last".equals(c.getName())){ 173 findC = c; 174 } 175 } 176 } 177 178 if(findC == null){ 179 //說明之前沒有看過書的記錄 180 ids += book.getId(); 181 }else{ 182 //說明之前有歷史看過的書的記錄,需要根據歷史記錄算一個新的記錄出來 183 String [] olds = findC.getValue().split(","); 184 StringBuffer buffer = new StringBuffer(); 185 buffer.append(book.getId()+","); 186 for(int i = 0;i<olds.length && buffer.toString().split(",").length<3 ;i++){ 187 String old = olds[i]; 188 if(!old.equals(book.getId())){ 189 buffer.append(old+","); 190 } 191 } 192 ids = buffer.substring(0, buffer.length()-1); 193 } 194 195 196 197 Cookie lastC = new Cookie("last",ids); 198 lastC.setMaxAge(3600*24*30); 199 lastC.setPath(request.getContextPath()); 200 response.addCookie(lastC); 201 } 202 203 public void doPost(HttpServletRequest request, HttpServletResponse response) 204 throws ServletException, IOException { 205 doGet(request, response); 206 } 207 208 }
4.Session:在服務器中,為瀏覽器創建獨一無二的內存空間,在其中保存會話相關的信息。
4.1session作為域使用:他是j2ee中四大域對象之一,作用范圍為整個會話。
4.2session的生命周期:在第一次調用reqeust.getSession()方法的時候,服務器會檢查是已經有對應的session,如果沒有就在內存中創建一個session並返回。
當一段時間內session沒有被使用,一般為30分鍾(此值可以在web.xml中配置<session-config>來配置,也可以使用TomcatManager進行配置),則服務器會銷毀該session
當服務器強行關閉時,沒有到期的session也會跟着銷毀。
如果調用session提供的invalidate(),可以立即銷毀session。
4.3session的原理:在服務器第一次調用request.getSession()方法的時候,會在內存中創建一個session對象,此對象具有一個獨一無二的id值,此id值將會以cookie(JSESSIONID)的形式發送給瀏覽器,瀏覽器以后每次訪問都會帶着此cookie,服務器就利用此cookie區分瀏覽器找到對應的session空間。
4.4同一電腦內的不同瀏覽器使用同一session:JSESSIONID這個cookie默認是保存在瀏覽器內存中的,我們可以自己創建一個同名同path的Cookie,並設置maxage值,使其被保存在硬盤中,從而實現統一電腦中不同瀏覽器公用一個JSESSIONID從而使用同一個session。
4.5使禁用Cookie的瀏覽器也可以使用session:由於session是基於cookie運行的,如果禁用了cookie則會導致session不可用,我們可以將提供給這種瀏覽器的所有的URL進行重寫,在所有的URL后跟上JSEESIONID,從而保證即使禁用了Cookie也能以URL的形式帶回JSESSIONID,從而可以使用session。要重寫所有的URL是一項成本很高的工作,一般我們不會這么做。
response. encodeRedirectURL(java.lang.String?url)如果此url是作為重定向操作的地址時使用此方法
response. encodeURL(java.lang.String?url)如果此url是普通連接則使用此方法
示例:利用session實現簡單的購物功能,並提供對同一台電腦上的多個瀏覽器共享session的支持以及對禁用cookie瀏覽器的支持。

1 BuyServlet.java 2 public class BuyServlet extends HttpServlet { 3 4 public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { 5 String prod = request.getParameter("prod"); 6 prod = new String(prod.getBytes("iso8859-1"),"utf-8"); 7 8 HttpSession session = request.getSession(); 9 10 Cookie jc = new Cookie("JSESSIONID",session.getId()); 11 jc.setPath(request.getContextPath()); 12 jc.setMaxAge(1800); 13 response.addCookie(jc); 14 15 session.setAttribute("prod", prod); 16 17 } 18 19 public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { 20 doGet(request, response); 21 } 22 23 } 24 25 PayServlet.java 26 public class PayServlet extends HttpServlet { 27 28 public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { 29 response.setContentType("text/html;charset=utf-8"); 30 HttpSession session = request.getSession(); 31 String prod = (String) session.getAttribute("prod"); 32 response.getWriter().write("您購買的是"+prod+"價值99999999999元"); 33 } 34 35 public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { 36 doGet(request, response); 37 } 38 39 }
示例:使用Session完成用戶登陸:當用戶登錄時在session中保存用戶名,在其他頁面就可以檢查session中是否存在用戶名,如果存在則認為已經登錄過。注銷的過程就是將session殺死的過程。
查看后期寫的完整的用戶登錄的做法!!!
示例:使用session完成防止表單重復提交:當提供表單頁面時,在表單中隱藏一個隨機數值,並且將該隨機數保存到session中,當表單提交時,檢查提交上來到隨機數與session中的隨機數是否相同,如果相同則允許注冊,注冊后立即刪除session中的隨機數,如果不同則認為是表單的重復提交。
*request.getSession()和request.getSession(false);的不同之處:前面的方法一調用,就會去檢查是否有對應的session,沒有就創建,有就取回。后面的方法只會去檢查,如果有就取回,如果沒有也不創建。
5.ServeltContext 、reqeust、session域的比較
servletContext 的作用域是整個web應用,隨着服務器啟動而創建,如果應用被移除出主機或服務器關閉則銷毀。
request 的作用域是整個請求鏈,每一次請求都會創建一個request,當請求結束時request銷毀。
session 的作用於是整個會話,第一次調用reqeust.getSession時創建,當一段時間沒有使用或服務器關閉或調用session.invalidate方法時銷毀
什么時候用ServeltContext什么時候用reqeust什么時候用session?
如果一個數據只是用來顯示的話就用request域
如果一個數據除了用來顯示以外我一會還要用,這時候用session
如果一個數據除了用來顯示以外還要給別人用,這時候用ServletContext域