什么是會話:用戶打開瀏覽器,點擊多個超鏈接,訪問服務器的多個web資源,然后關閉瀏覽器,整個過程就稱為一個會話;
會話過程需要解決的問題:每個用戶在使用瀏覽器與服務器進行會話的過程中,都可能會產生一些數據,這些輸入如何來進行保存?比如用戶在購物網站瀏覽的商品記錄,用戶添加購物車的記錄等等這些信息如何進行存儲?在程序中會話跟蹤是一件非常重要的事情,一個用戶的所有請求操作都應該屬於同一個會話,而另一個人的所有請求操作應該屬於另一個人,二者不能混淆!當想到需要在保存數據時,我們首先肯定會想到使用域對象,這些數據是否可以使用Request或者ServletContext對象來保存呢?
首先我們舉例說明:登錄的場景
1 Context對象:
小張: 輸入“張三” (保存數據: context.setAttribute("name","張三")) -> 用戶主頁(顯示“張三”)
小李: 輸入“李四”(保存數據:context.setAttribute("name","李四")) -> 用戶主頁(顯示“李四”)
context是所有用戶公有的資源,因此當小李登錄后,用戶主頁將全部顯示為“李四”,新的數據將會覆蓋原有的數據,因此不能夠使用context對象;
2 Request對象:該對象只在同一個頁面有效,當需要進行頁面跳轉的時候,顯然必須使用轉發技術來實現,因此Request對象也不能夠有效解決該問題。
會話技術
為了解決上述的問題,這里引入了會話技術,其中會話技術主要分為兩個部分,cookie技術和session技術,前者將數據保存在客戶端,后者將數據保存在服務器。
- Cookie技術:Cookie是客戶端技術,服務器把每個用戶的數據以cookie的形式寫給用戶各自的瀏覽器。當用戶使用瀏覽器再去訪問服務器中的web資源時,就會帶着各自的數據去。這樣,web資源處理的就是用戶各自的數據了。
- Session技術:Session是服務器端技術,利用這個技術,服務器在運行時可以為每一個用戶的瀏覽器創建一個其獨享的session對象,由於session為用戶瀏覽器獨享,所以用戶在訪問服務器的web資源時,可以把各自的數據放在各自的session中,當用戶再去訪問服務器中的其它web資源時,其它web資源再從用戶各自的session中取出數據為用戶服務。
(一) Cookie技術
1.1 Cookie技術核心
Cookie類:用於存儲會話數據
1)構造Cookie對象
- Cookie(java.lang.String name, java.lang.String value)
2)設置cookie
- void setPath(java.lang.String uri) :設置cookie的有效訪問路徑
- void setMaxAge(int expiry) : 設置cookie的有效時間
- void setValue(java.lang.String newValue) :設置cookie的值
3)發送cookie到瀏覽器端保存
- void response.addCookie(Cookie cookie) : 發送cookie
4)服務器接收cookie:
- Cookie[] request.getCookies() : 接收cookie,返回所有cookie的數據信息
1.2 Cookie原理
1)服務器創建cookie對象,把會話數據存儲到cookie對象中。
- new Cookie("name","value");
2)服務器發送cookie信息到瀏覽器
- response.addCookie(cookie);
舉例: set-cookie: name=Infaraway ( 隱藏發送了一個set-cookie名稱的響應頭 )
3)瀏覽器得到服務器發送的cookie,然后保存在瀏覽器端。
4)瀏覽器在下次訪問服務器時,會帶着cookie信息
舉例: cookie: name=Infaraway (隱藏帶着一個叫cookie名稱的請求頭)
5)服務器接收到瀏覽器帶來的cookie信息
- request.getCookies();
1 package com.infaraway.servlet; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import java.io.IOException; 8 9 /** 10 * Created by :Infaraway 11 * DATE : 2017/3/25 12 * Time : 22:08 13 * Funtion : cookie測試 14 */ 15 public class CreateCookie { 16 public void doGet(HttpServletRequest request, HttpServletResponse response) 17 throws ServletException, IOException { 18 //1.創建Cookie對象 19 Cookie cookie1 = new Cookie("name","Infaraway"); 20 //Cookie cookie2 = new Cookie("email","Infaraway@qq.com"); 21 //Cookie cookie1 = new Cookie("email","Infaraway@qq.com"); 22 23 /** 24 * 1)設置cookie的有效路徑。默認情況:有效路徑在當前web應用下。 /cookie 25 */ 26 //cookie1.setPath("/cookie"); 27 //cookie2.setPath("/basisJavaWeb"); 28 /** 29 * 2)設置cookie的有效時間 30 * 正整數:表示cookie數據保存瀏覽器的緩存目錄(硬盤中),數值表示保存的時間。 31 * 負整數:表示cookie數據保存瀏覽器的內存中。瀏覽器關閉cookie就丟失了! 32 * 零:表示刪除同名的cookie數據 33 */ 34 //cookie1.setMaxAge(20); //20秒,從最后不調用cookie開始計算 35 cookie1.setMaxAge(-1); //cookie保存在瀏覽器內存(會話cookie) 36 //cookie1.setMaxAge(0); 37 38 //2.把cookie數據發送到瀏覽器(通過響應頭發送: set-cookie名稱) 39 //response.setHeader("set-cookie", cookie.getName()+"="+cookie.getValue()+",email=eric@qq.com"); 40 //推薦使用這種方法,避免手動發送cookie信息 41 response.addCookie(cookie1); 42 //response.addCookie(cookie2); 43 //response.addCookie(cookie1); 44 45 //3.接收瀏覽器發送的cookie信息 46 /*String name = request.getHeader("cookie"); 47 System.out.println(name);*/ 48 Cookie[] cookies = request.getCookies(); 49 //注意:判斷null,否則空指針 50 if(cookies!=null){ 51 //遍歷 52 for(Cookie c:cookies){ 53 String name = c.getName(); 54 String value = c.getValue(); 55 System.out.println(name+"="+value); 56 } 57 }else{ 58 System.out.println("沒有接收cookie數據"); 59 } 60 } 61 }
1.3 Cookie的細節
1)void setPath(java.lang.String uri) :設置cookie的有效訪問路徑。有效路徑指的是cookie的有效路徑保存在哪里,那么瀏覽器在有效路徑下訪問服務器時就會帶着cookie信息,否則不帶cookie信息。
2)void setMaxAge(int expiry) : 設置cookie的有效時間。
- 正整數:表示cookie數據保存瀏覽器的緩存目錄(硬盤中),數值表示保存的時間。
- 負整數:表示cookie數據保存瀏覽器的內存中。瀏覽器關閉cookie就丟失了!!
- 零:表示刪除同名的cookie數據
3)Cookie數據類型只能保存非中文字符串類型的。可以保存多個cookie,但是瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。
1.4 案例-查看用戶瀏覽器過的商品
首先是商品的實體類:

1 package com.infaraway.entity; 2 3 4 /** 5 * Created by :Infaraway 6 * DATE : 2017/3/25 7 * Time : 14:34 8 * Funtion : 商品類 9 */ 10 public class Product { 11 12 private String id; 13 private String proName; 14 private String proType; 15 private double price; 16 public String getId() { 17 return id; 18 } 19 public void setId(String id) { 20 this.id = id; 21 } 22 public String getProName() { 23 return proName; 24 } 25 public void setProName(String proName) { 26 this.proName = proName; 27 } 28 public String getProType() { 29 return proType; 30 } 31 public void setProType(String proType) { 32 this.proType = proType; 33 } 34 public double getPrice() { 35 return price; 36 } 37 public void setPrice(double price) { 38 this.price = price; 39 } 40 public Product(String id, String proName, String proType, double price) { 41 super(); 42 this.id = id; 43 this.proName = proName; 44 this.proType = proType; 45 this.price = price; 46 } 47 public Product() { 48 super(); 49 // TODO Auto-generated constructor stub 50 } 51 @Override 52 public String toString() { 53 return "Product [id=" + id + ", price=" + price + ", proName=" 54 + proName + ", proType=" + proType + "]"; 55 } 56 57 }
這里我們使用list集合來模擬數據庫,使用dao層進行增刪改查操作

1 package com.infaraway.dao; 2 3 4 import com.infaraway.entity.Product; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 10 /** 11 * Created by :Infaraway 12 * DATE : 2017/3/25 13 * Time : 14:34 14 * Funtion : 商品的增刪改查操作 15 */ 16 public class ProductDao { 17 //模擬數據庫,存儲所有的商品信息 18 private static List<Product> data = new ArrayList<Product>(); 19 20 /** 21 * 靜態代碼塊,初始化 22 */ 23 static{ 24 //初始化 25 for(int i=1;i<=10;i++){ 26 data.add(new Product(""+i,"筆記本"+i,"LN00"+i,334.0+i)); 27 } 28 } 29 30 31 /** 32 * 顯示所有的商品 33 */ 34 public List<Product> findAll(){ 35 return data; 36 } 37 38 /** 39 * 根據id查找商品 40 */ 41 public Product findById(String id){ 42 for(Product p:data){ 43 if(p.getId().equals(id)){ 44 return p; 45 } 46 } 47 return null; 48 } 49 50 }
然后給出商品列表界面,由用戶點擊選擇瀏覽的商品,並且給出了用戶的瀏覽記錄

1 package com.infaraway.servlet; 2 3 import com.infaraway.dao.ProductDao; 4 import com.infaraway.entity.Product; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.Cookie; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 import java.io.IOException; 12 import java.io.PrintWriter; 13 import java.util.List; 14 15 /** 16 * Created by :Infaraway 17 * DATE : 2017/3/25 18 * Time : 14:34 19 * Funtion : 20 */ 21 public class ListServlet extends HttpServlet { 22 @Override 23 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 24 25 resp.setContentType("text/html;charset=utf-8"); 26 ProductDao dao = new ProductDao(); 27 List<Product> productList = dao.findAll(); 28 29 //2.把商品顯示到瀏覽器 30 PrintWriter writer = resp.getWriter(); 31 String html = ""; 32 33 html += "<html>"; 34 html += "<head>"; 35 html += "<title>顯示商品列表</title>"; 36 html += "</head>"; 37 html += "<body>"; 38 html += "<table border='1' align='center' width='600px'>"; 39 html += "<tr>"; 40 html += "<th>編號</th><th>商品名稱</th><th>商品型號</th><th>商品價格</th>"; 41 html += "</tr>"; 42 //遍歷商品 43 if(productList!=null){ 44 for(Product p:productList){ 45 html += "<tr>"; 46 // /day11_hist/DetailServlet?id=1 訪問DetailSErvlet的servlet程序,同時傳遞 名為id,值為1 的參數 47 html += "<td>"+p.getId()+"</td><td><a href='"+req.getContextPath()+"/DetailServlet?id="+p.getId()+"'>"+p.getProName()+"</a></td><td>"+p.getProType()+"</td><td>"+p.getPrice()+"</td>"; 48 html += "<tr>"; 49 } 50 } 51 html += "</table>"; 52 53 54 /** 55 * 顯示瀏覽過的商品 56 */ 57 html += "最近瀏覽過的商品:<br/>"; 58 //取出prodHist的cookie 59 Cookie[] cookies = req.getCookies(); 60 if(cookies!=null){ 61 for (Cookie cookie : cookies) { 62 if(cookie.getName().equals("prodHist")){ 63 String prodHist = cookie.getValue(); // 3,2,1 64 String[] ids = prodHist.split(","); 65 //遍歷瀏覽過的商品id 66 for (String id : ids) { 67 //查詢數據庫,查詢對應的商品 68 Product p = dao.findById(id); 69 //顯示到瀏覽器 70 html += ""+p.getId()+" "+p.getProName()+" "+p.getPrice()+"<br/>"; 71 } 72 } 73 } 74 } 75 76 77 html += "</body>"; 78 html += "</html>"; 79 80 writer.write(html); 81 82 } 83 84 @Override 85 public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 86 super.doGet(req, resp); 87 } 88 }
最后給出商品詳情界面

1 package com.infaraway.servlet; 2 3 4 import com.infaraway.dao.ProductDao; 5 import com.infaraway.entity.Product; 6 7 import java.io.IOException; 8 import java.io.PrintWriter; 9 import java.util.Arrays; 10 import java.util.Collection; 11 import java.util.LinkedList; 12 13 import javax.servlet.ServletException; 14 import javax.servlet.http.Cookie; 15 import javax.servlet.http.HttpServlet; 16 import javax.servlet.http.HttpServletRequest; 17 import javax.servlet.http.HttpServletResponse; 18 /** 19 * Created by :Infaraway 20 * DATE : 2017/3/25 21 * Time : 14:34 22 * Funtion : 商品信息詳細頁面 23 */ 24 public class DetailServlet extends HttpServlet { 25 26 public void doGet(HttpServletRequest request, HttpServletResponse response) 27 throws ServletException, IOException { 28 response.setContentType("text/html;charset=utf-8"); 29 //1.獲取傳來的商品id 30 String id = request.getParameter("id"); 31 32 //2. 創建dao,進行商品的操作 33 ProductDao dao = new ProductDao(); 34 Product product = dao.findById(id); 35 36 //3.將商品信息打印到瀏覽器頁面 37 PrintWriter writer = response.getWriter(); 38 String html = ""; 39 40 html += "<html>"; 41 html += "<head>"; 42 html += "<title>商品詳情</title>"; 43 html += "</head>"; 44 html += "<body>"; 45 html += "<table border='1' align='center' width='300px'>"; 46 if(product!=null){ 47 html += "<tr><th>編號:</th><td>"+product.getId()+"</td></tr>"; 48 html += "<tr><th>商品名稱:</th><td>"+product.getProName()+"</td></tr>"; 49 html += "<tr><th>商品類型:</th><td>"+product.getProType()+"</td></tr>"; 50 html += "<tr><th>商品價格:</th><td>"+product.getPrice()+"</td></tr>"; 51 } 52 53 html += "</table>"; 54 html += "<center><a href='"+request.getContextPath()+"/ListServlet'>[返回列表]</a></center>"; 55 html += "</body>"; 56 html += "</html>"; 57 58 writer.write(html); 59 60 61 /** 62 * 將瀏覽過的商品存入cookie 63 */ 64 //1.創建cookie 65 Cookie cookie = new Cookie("prodHist",createValue(request,id)); 66 cookie.setMaxAge(1*30*24*60*60);//設置cookie有效時間(單位:s) 67 //2.保存cookie 68 response.addCookie(cookie); 69 } 70 71 /** 72 * 生成cookie的值信息 73 * 當前cookie值 --> 傳入的商品id --> 最終的cookie值 74 * null或沒有prodHist 1 1 (算法: 直接返回傳入的id ) 75 * 1 2 2,1 (沒有重復且小於3個。算法:直接把傳入的id放最前面 ) 76 * 2,1 1 1,2(有重復且小於3個。算法:去除重復id,把傳入的id放最前面 ) 77 * 3,2,1 2 2,3,1(有重復且3個。算法:去除重復id,把傳入的id放最前面) 78 * 3,2,1 4 4,3,2(沒有重復且3個。算法:去最后的id,把傳入的id放最前面) 79 */ 80 private String createValue(HttpServletRequest request,String id) { 81 82 Cookie[] cookies = request.getCookies(); 83 String prodHist = null; 84 if(cookies!=null){ 85 for (Cookie cookie : cookies) { 86 if(cookie.getName().equals("prodHist")){ 87 prodHist = cookie.getValue(); 88 break; 89 } 90 } 91 } 92 93 94 // null或沒有prodHist 95 if(cookies==null || prodHist==null){ 96 //直接返回傳入的id 97 return id; 98 } 99 100 // 3,21 2 101 //String -> String[] -> Collection :為了方便判斷重復id 102 String[] ids = prodHist.split(","); 103 Collection<String> colls = Arrays.asList(ids); //<3,21> 104 // LinkedList 方便地操作(增刪改元素)集合 105 // Collection -> LinkedList 106 LinkedList<String> list = new LinkedList<String>(colls); 107 108 109 //不超過3個 110 if(list.size()<3){ 111 //id重復 112 if(list.contains(id)){ 113 //去除重復id,把傳入的id放最前面 114 list.remove(id); 115 list.addFirst(id); 116 }else{ 117 //直接把傳入的id放最前面 118 list.addFirst(id); 119 } 120 }else{ 121 //等於3個 122 //id重復 123 if(list.contains(id)){ 124 //去除重復id,把傳入的id放最前面 125 list.remove(id); 126 list.addFirst(id); 127 }else{ 128 //去最后的id,把傳入的id放最前面 129 list.removeLast(); 130 list.addFirst(id); 131 } 132 } 133 134 // LinedList -> String 135 StringBuffer sb = new StringBuffer(); 136 for (Object object : list) { 137 sb.append(object+","); 138 } 139 //去掉最后的逗號 140 String result = sb.toString(); 141 result = result.substring(0, result.length()-1); 142 return result; 143 } 144 145 public void doPost(HttpServletRequest request, HttpServletResponse response) 146 throws ServletException, IOException { 147 doGet(request, response); 148 } 149 150 }
上述代碼可以在這里找到:https://git.oschina.net/infaraway/basisJavaWeb/tree/master/cookie
(二) Session技術
2.1 引入
Cookie的局限:
- 1)Cookie只能存字符串類型。不能保存對象
- 2)只能存非中文。
- 3)1個Cookie的容量不超過4KB。
如果要保存非字符串,超過4kb內容,只能使用session技術!
Session特點:會話數據保存在服務器端。(內存中)
2.2 Session技術核心
HttpSession類:用於保存會話數據
1)創建或得到session對象
- HttpSession getSession()
- HttpSession getSession(boolean create)
2)設置session對象
- void setMaxInactiveInterval(int interval) : 設置session的有效時間
- void invalidate() : 銷毀session對象
- java.lang.String getId() : 得到session編號
3)保存會話數據到session對象
- void setAttribute(java.lang.String name, java.lang.Object value) : 保存數據
- java.lang.Object getAttribute(java.lang.String name) : 獲取數據
- void removeAttribute(java.lang.String name) : 清除數據
2.3 Session原理
問題: 服務器能夠識別不同的瀏覽者!
關鍵: 在哪個session域對象保存數據,就必須從哪個域對象取出!
代碼解讀:HttpSession session = request.getSession();
1)第一次訪問創建session對象,給session對象分配一個唯一的ID,叫JSESSIONID
new HttpSession();
2)把JSESSIONID作為Cookie的值發送給瀏覽器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3)第二次訪問的時候,瀏覽器帶着JSESSIONID的cookie訪問服務器
4)服務器得到JSESSIONID,在服務器的內存中搜索是否存放對應編號的session對象。
if(找到){
return map.get(sessionID);
}
Map<String,HttpSession>
5)如果找到對應編號的session對象,直接返回該對象
6)如果找不到對應編號的session對象,創建新的session對象,繼續走1的流程
結論:通過JSESSION的cookie值在服務器找session對象!
1 import java.io.IOException; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 import javax.servlet.http.HttpSession; 9 /** 10 * Created by :Infaraway 11 * DATE : 2017/3/25 12 * Time : 14:34 13 * Funtion : session對象的創建 14 */ 15 public class CreateSession extends HttpServlet { 16 17 public void doGet(HttpServletRequest request, HttpServletResponse response) 18 throws ServletException, IOException { 19 //1.創建session對象 20 HttpSession session = request.getSession(); 21 //得到session編號 22 System.out.println("id="+session.getId()); 23 //修改session的有效時間 24 //session.setMaxInactiveInterval(20); 25 26 //手動發送一個硬盤保存的cookie給瀏覽器 27 Cookie c = new Cookie("JSESSIONID",session.getId()); 28 c.setMaxAge(60*60); 29 response.addCookie(c); 30 31 //2.保存會話數據 32 session.setAttribute("name", "Infaraway"); 33 } 34 }
1 package com.infaraway.servlet; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 import java.io.IOException; 9 10 /** 11 * Created by :Infaraway 12 * DATE : 2017/3/25 13 * Time : 22:17 14 * Funtion : 15 */ 16 public class DeleteCookie extends HttpServlet { 17 18 public void doGet(HttpServletRequest request, HttpServletResponse response) 19 throws ServletException, IOException { 20 /** 21 * 需求: 刪除cookie 22 */ 23 Cookie cookie = new Cookie("name","xxxx"); 24 cookie.setMaxAge(0);//刪除同名的cookie 25 response.addCookie(cookie); 26 System.out.println("刪除成功"); 27 28 } 29 }
2.4 Sesson細節
1)java.lang.String getId() : 得到session編號
2)兩個getSession方法:
- getSession(true) / getSession() : 創建或得到session對象。沒有匹配的session編號,自動創 建新的session對象。
- getSession(false): 得到session對象。沒有匹配的session編號,返回null
3)void setMaxInactiveInterval(int interval) : 設置session的有效時間
session對象銷毀時間:
- 1 默認情況30分服務器自動回收
- 2 修改session回收時間
- 3 全局修改session有效時間
1 <!-- 修改session全局有效時間:分鍾 --> 2 <session-config> 3 <session-timeout>1</session-timeout> 4 </session-config>
- 4.手動銷毀session對象
void invalidate() : 銷毀session對象
4)如何避免瀏覽器的JSESSIONID的cookie隨着瀏覽器關閉而丟失的問題?
瀏覽器關閉而丟失cookie的原因是cookie的有效時間設置中參數為負整數導致,因此需求使用setMaxAge()函數將時間修正為正整數即可。
1 /** 2 * 手動發送一個硬盤保存的cookie給瀏覽器 3 */ 4 Cookie c = new Cookie("JSESSIONID",session.getId()); 5 c.setMaxAge(60*60); 6 response.addCookie(c);
上述代碼可以在這里找到:https://git.oschina.net/infaraway/basisJavaWeb/tree/master/session
總結:
1)會話管理: 瀏覽器和服務器會話過程中的產生的會話數據的管理。
2)Cookie技術:
- new Cookie("name","value")
- response.addCookie(coookie)
- request.getCookies()
3)Session技術
- request.getSession();
- setAttrbute("name","會話數據");
- getAttribute("會話數據")