什麼是會話技術
基本概念: 指使用者開一個瀏覽器,訪問一個網站,只要不關閉該瀏覽器,不管該使用者點選多少個超連結,訪問多少資源,直到使用者關閉瀏覽器,整個這個過程我們稱為一次會話.
為什麼我們要使用會話技術?
會話跟蹤技術可以解決我們很多很多問題。
- 在論壇登陸的時候,很多時候會有一個小框框問你是否要自動登陸,當你下次登陸的時候就不用輸入密碼了
- 根據我以前瀏覽過的商品,猜我喜歡什麼商品
Cookie
會話跟蹤技術有Cookie和Session,Cookie技術是先出現的。我們先講Cookie技術吧。
什麼是Cookie
Cookie是由W3C組織提出,最早由netscape社群發展的一種機制
- 網頁之間的互動是通過HTTP協議傳輸資料的,而Http協議是無狀態的協議。無狀態的協議是什麼意思呢?一旦資料提交完後,瀏覽器和伺服器的連線就會關閉,再次互動的時候需要重新建立新的連線。
- 伺服器無法確認使用者的資訊,於是乎,W3C就提出了:給每一個使用者都發一個通行證,無論誰訪問的時候都需要攜帶通行證,這樣伺服器就可以從通行證上確認使用者的資訊。通行證就是Cookie
Cookie的流程:瀏覽器訪問伺服器,如果伺服器需要記錄該使用者的狀態,就使用response向瀏覽器傳送一個Cookie,瀏覽器會把Cookie儲存起來。當瀏覽器再次訪問伺服器的時候,瀏覽器會把請求的網址連同Cookie一同交給伺服器。
Cookie API
- Cookie類用於建立一個Cookie物件
- response介面中定義了一個addCookie方法,它用於在其響應頭中增加一個相應的Set-Cookie頭欄位
- request介面中定義了一個getCookies方法,它用於獲取客戶端提交的Cookie
常用的Cookie方法:
- public Cookie(String name,String value)
- setValue與getValue方法
- setMaxAge與getMaxAge方法
- setPath與getPath方法
- setDomain與getDomain方法
- getName方法
簡單使用Cookie
- 建立Cookie物件,傳送Cookie給瀏覽器、
//設定response的編碼
response.setContentType("text/html;charset=UTF-8");
//建立Cookie物件,指定名稱和值
Cookie cookie = new Cookie("username", "zhongfucheng");
//向瀏覽器給一個Cookie
response.addCookie(cookie);
response.getWriter().write("我已經向瀏覽器傳送了一個Cookie");
複製程式碼
- 瀏覽器本身沒有任何Cookie
- 訪問Servlet1,再回到資料夾中,還是沒有發現Cookie,這是為什麼呢?我明明向瀏覽器傳送了一個Cookie的。
- 原來傳送Cookie給瀏覽器是需要設定Cookie的時間的。在給瀏覽器之前,設定一下Cookie的時間
//設定Cookie的時間
cookie.setMaxAge(1000);
複製程式碼
- 再次訪問,已經發現資料夾中多了個Cookie的文字了
Cookie細節
Cookie不可跨域名性
- 很多人在初學的時候可能有一個疑問:在訪問Servlet的時候瀏覽器是不是把所有的Cookie都帶過去給伺服器,會不會修改了別的網站的Cookie
- 答案是否定的。Cookie具有不可跨域名性。瀏覽器判斷一個網站是否能操作另一個網站的Cookie的依據是域名。所以一般來說,當我訪問baidu的時候,瀏覽器只會把baidu頒發的Cookie帶過去,而不會帶上google的Cookie。
Cookie儲存中文
- 上面我們的例子儲存的是英文字元,下面我們來看下儲存中文字元會怎麼樣。
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
String name = "中國";
Cookie cookie = new Cookie("country", name);
cookie.setMaxAge(2000);
response.addCookie(cookie);
printWriter.write("我頒發了一個Cookie,值儲存的是中文資料");
複製程式碼
- 訪問Servlet1,好吧。出異常了!
- 中文屬於Unicode字元,英文資料ASCII字元,中文佔4個字元或者3個字元,英文佔2個字元。
- 解決:Cookie使用Unicode字元時需要對Unicode字元進行編碼。
//對Unicode字元進行編碼
Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
複製程式碼
- 再次訪問Servlet1,已經把Cookie成功頒發給瀏覽器了
- 我們發現Cookie儲存在硬碟的中文資料是經過編碼的,那麼我們在取出Cookie的時候要對中文資料進行解碼
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
String name = cookies[i].getName();
//經過URLEncoding就要URLDecoding
String value = URLDecoder.decode(cookies[i].getValue(), "UTF-8");
printWriter.write(name + "------" + value);
}
複製程式碼
- 取出存進Cookie的值
Cookie的有效期
Cookie的有效期是通過setMaxAge()來設定的。
- 如果MaxAge為正數,瀏覽器會把Cookie寫到硬碟中,只要還在MaxAge秒之前,登陸網站時該Cookie就有效【不論關閉了瀏覽器還是電腦】
- 如果MaxAge為負數,Cookie是臨時性的,僅在本瀏覽器內有效,關閉瀏覽器Cookie就失效了,Cookie不會寫到硬碟中。Cookie預設值就是-1。這也就為什麼在我第一個例子中,如果我沒設定Cookie的有效期,在硬碟中就找不到對應的檔案。
- 如果MaxAge為0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie對應的方法,把MaxAge設定為0等同於刪除Cookie
Cookie的修改和刪除
- 上面我們已經知道了Cookie機制沒有提供刪除Cookie的方法。其實細心點我們可以發現,Cookie機制也沒有提供修改Cookie的方法。那麼我們怎麼修改Cookie的值呢?
- Cookie儲存的方式類似於Map集合,如下圖所示
-
Cookie的名稱相同,通過response新增到瀏覽器中,會覆蓋原來的Cookie。
-
以country為名儲存的是%E4%B8%AD%E5%9B%BD,下面我再以country為名,把值改變一下。
String name = "看完部落格就點贊";
//對Unicode字元進行編碼
Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
複製程式碼
- 再次檢視Cookie的時候,值已經改變了,但是檔案還是那一份
- 現在我要刪除該Cookie,把MaxAge設定為0,並新增到瀏覽器中即可
String name = "看完部落格就點贊";
//對Unicode字元進行編碼
Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
//一定不要忘記新增到瀏覽器中
cookie.setMaxAge(0);
response.addCookie(cookie);
printWriter.write("我刪除了該Cookie");
複製程式碼
- 訪問Servlet,在硬碟已經找不到Cookie的檔案了!
-
注意:刪除,修改Cookie時,新建的Cookie除了value、maxAge之外的所有屬性都要與原Cookie相同。否則瀏覽器將視為不同的Cookie,不予覆蓋,導致刪除修改失敗!
-
我們來試驗一下把。
String name = "看完部落格就點贊";
//對Unicode字元進行編碼
Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
//一定不要忘記新增到瀏覽器中
cookie.setMaxAge(10000);
response.addCookie(cookie);
複製程式碼
- 上面新建了一個Cookie,我修改下Cookie的其他屬性,再刪除,看能否把Cookie刪除掉
//一定不要忘記新增到瀏覽器中
cookie.setPath("/ouzicheng");
cookie.setMaxAge(0);
response.addCookie(cookie);
printWriter.write("刪除一個Cookie");
複製程式碼
- 結果Cookie還在硬碟中
Cookie的域名###
Cookie的domain屬性決定執行訪問Cookie的域名。domain的值規定為“.域名”
-
Cookie的隱私安全機制決定Cookie是不可跨域名的。也就是說www.baidu.com和www.google.com之間的Cookie是互不交接的。即使是同一級域名,不同二級域名也不能交接,也就是說:www.goole.com和www.image.goole.com的Cookie也不能訪問
-
我在本地上配置了3個虛擬主機,localhost,www.zhongfucheng.com,www.image.zhongfucheng.com【如果不知道怎麼配置,在我Tomcat的部落格有】
- 我用www.zhongfucheng.com域名傳送了一個Cookie給瀏覽器
Cookie cookie = new Cookie("name", "zhongfucheng");
cookie.setMaxAge(1000);
response.addCookie(cookie);
printWriter.write("使用www.zhongfucheng.com域名新增了一個Cookie");
複製程式碼
- 首先,證明了Cookie不可跨名性,localhost域名拿不到www.zhongfucheng.com頒發給瀏覽器的Cookie
- 再使用www.image.zhongfucheng.com域名訪問,證明即使一級域名相同,二級域名不同,也不能獲取到Cookie
- 當然,使用www.zhongfucheng.com當然能獲取到Cookie,Cookie通過請求頭帶給伺服器
- 現在我希望一級域名相同的網頁Cookie之間可以相互訪問。也就是說www.image.zhongfucheng.com可以獲取到www.zhongfucheng.com的Cookie就需要使用到domain方法。
Cookie cookie = new Cookie("name", "ouzicheng");
cookie.setMaxAge(1000);
cookie.setDomain(".zhongfucheng.com");
response.addCookie(cookie);
printWriter.write("使用www.zhongfucheng.com域名新增了一個Cookie,只要一級是zhongfucheng.com即可訪問");
複製程式碼
-
使用www.zhongfucheng.com釋出一個Cookie
-
使用www.image.zhongfucheng.com域名訪問一下。發現可以獲取到Cookie了
Cookie的路徑
Cookie的path屬性決定允許訪問Cookie的路徑
-
一般地,Cookie釋出出來,整個網頁的資源都可以使用。現在我只想Servlet1可以獲取到Cookie,其他的資源不能獲取。
-
使用Servlet2頒發一個Cookie給瀏覽器,設定路徑為"/Servlet1"。
Cookie cookie = new Cookie("username", "java");
cookie.setPath("/Servlet1");
cookie.setMaxAge(1000);
response.addCookie(cookie);
printWriter.write("該Cookie只有Servlet1獲取得到");
複製程式碼
- 使用Servlet3訪問伺服器,看看瀏覽器是否把Cookie帶上。顯然,瀏覽器訪問Servlet3並沒有把Cookie帶上。
- 使用Servlet1訪問伺服器,看看瀏覽器是否把Cookie帶上。答案是肯定的!
Cookie的安全屬性
- HTTP協議不僅僅是無狀態的,而且是不安全的!如果不希望Cookie在非安全協議中傳輸,可以設定Cookie的secure屬性為true,瀏覽器只會在HTTPS和SSL等安全協議中傳輸該Cookie。
- 當然了,設定secure屬性不會將Cookie的內容加密。如果想要保證安全,最好使用md5演算法加密【後面有】。
Cookie的應用
顯示使用者上次訪問的時間
-
其實就是每次登陸的時候,取到Cookie儲存的值,再更新下Cookie的值。
-
訪問Serlvet有兩種情況
- 第一次訪問
- 已經訪問過了
-
全部程式碼如下:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
//獲取網頁上所有的Cookie
Cookie[] cookies = request.getCookies();
//判斷Cookie的值是否為空
String cookieValue = null;
for (int i = 0; cookies != null && i < cookies.length; i++) {
//獲取到以time為名的Cookie
if (cookies[i].getName().equals("time")) {
printWriter.write("您上次登陸的時間是:");
cookieValue = cookies[i].getValue();
printWriter.write(cookieValue);
cookies[i].setValue(simpleDateFormat.format(new Date()));
response.addCookie(cookies[i]);
//既然已經找到了就可以break迴圈了
break;
}
}
//如果Cookie的值是空的,那麼就是第一次訪問
if (cookieValue == null) {
//建立一個Cookie物件,日期為當前時間
Cookie cookie = new Cookie("time", simpleDateFormat.format(new Date()));
//設定Cookie的生命期
cookie.setMaxAge(20000);
//response物件回送Cookie給瀏覽器
response.addCookie(cookie);
printWriter.write("您是第一次登陸啊!");
}
複製程式碼
- 按照正常的邏輯來寫,程式流程應該是這樣子的。先建立Cookie物件,回送Cookie給瀏覽器。再遍歷Cookie,更新Cookie的值。
-
但是,按照上面的邏輯是做不到的!因為每次訪問Servlet的時候都會覆蓋原來的Cookie,取到Cookie的值永遠都是當前時間,而不是上次儲存的時間。
-
我們換一個邏輯寫:先檢查(遍歷)所有Cookie有沒有我要的,如果得不到我想要的Cookie,Cookie的值是null,那麼就是第一次登陸,於是就有了上面的程式碼了。
-
我們來看下效果吧!當我第一次登陸的時候
- Cookie儲存在硬碟中。
- 再次訪問Servlet。明顯地,取到的就是Cookie的值
顯示上次瀏覽過商品
- 我就以書籍為例子了!首先設計Book物件
private String id ;
private String name ;
private String author;
public Book() {
}
public Book(String id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}
...各種set、get方法
複製程式碼
- 設計一個簡單的資料庫儲存資料。就用LinkedHashMap集合【根據商品的id找書籍所以用Map,刪改較多所以用Linked】
private static LinkedHashMap<String, Book> linkedHashMap = new LinkedHashMap();
//簡化開發複雜度,book的id和商品的id相同
static {
linkedHashMap.put("1", new Book("1", "javaweb", "zhong"));
linkedHashMap.put("2", new Book("2", "java", "fu"));
linkedHashMap.put("3", new Book("3", "oracle", "cheng"));
linkedHashMap.put("4", new Book("4", "mysql", "ou"));
linkedHashMap.put("5", new Book("5", "ajax", "zi"));
}
//獲取到所有書籍
public static LinkedHashMap getAll() {
return linkedHashMap;
}
複製程式碼
- 顯示網頁上所有的書籍【首頁】
printWriter.write("網頁上所有的書籍:"+"<br/>");
//拿到資料庫所有的書
LinkedHashMap<String, Book> linkedHashMap = DB.getAll();
Set<Map.Entry<String, Book>> entry = linkedHashMap.entrySet();
//顯示所有的書到網頁上
for (Map.Entry<String, Book> stringBookEntry : entry) {
Book book = stringBookEntry.getValue();
printWriter.write(book.getId() +" "+ book.getName()+"<br/>");
}
複製程式碼
- 接著,我們要做的就是給顯示的書籍掛上一個超連結,當使用者點選想看的書籍時,就跳轉到該書籍的詳細資訊頁面
- 超連結應該把書的id傳遞過去,不然處理頁面是不知道使用者想看的是哪一本書的!
//顯示所有的書到網頁上
for (Map.Entry<String, Book> stringBookEntry : entry) {
Book book = stringBookEntry.getValue();
printWriter.write("<a href='/ouzicheng/Servlet2?id=" + book.getId() + "''target=_blank' +" + book.getName() + "</a>");
printWriter.write("<br/>");
}
複製程式碼
- 接收id,找到使用者想要看哪一本書,輸出該書的詳細資訊
String id = request.getParameter("id");
//由於book的id和商品的id是一致的。獲取到使用者點選的書
Book book = (Book) DB.getAll().get(id);
//輸出書的詳細資訊
printWriter.write("書的編號是:" + book.getId()+"<br/>");
printWriter.write("書的名稱是:" + book.getName()+"<br/>");
printWriter.write("書的作者是:" + book.getAuthor()+"<br/>");
複製程式碼
- 點選想要的書籍。
- 得到書籍的詳細資訊
-
既然使用者點選了書籍,那麼伺服器就應該頒發Cookie給瀏覽器,記住使用者點選了該書籍
-
現在問題來了,Cookie的值應該是什麼呢?試想一下,待會還要把瀏覽過的書籍顯示出來,所以用書籍的id是最好不過的。想到了用書籍的id作為Cookie的值,我們還要定義一些規則!
-
我們可能有非常多的書籍,不可能把使用者瀏覽過的書籍都顯示出來。所以我們定義只能顯示3本瀏覽過的書籍
-
書籍的id都是數字,如果不做任何修改,存到Cookie裡邊可能就是231,345,123此類的數字,這樣取出某一個id的時候就十分費勁並且後面還要判斷該書是否存在Cookie裡邊了,所以我們要把儲存到Cookie的書籍id分割起來。所以我們定義”_“作為分隔符
-
按上面的應用,我們的邏輯應該是:先遍歷下Cookie,看下有沒有我們想要的Cookie。如果找到想要的Cookie,那就取出Cookie的值
String bookHistory = null;
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
bookHistory = cookies[i].getValue();
}
}
複製程式碼
-
取出了Cookie的值也分幾種情況
- Cookie的值為null【直接把傳入進來的id當做是Cookie的值】
- Cookie的值長度有3個了【把排在最後的id去掉,把傳進來的id排在最前邊】
- Cookie的值已經包含有傳遞進來的id了【把已經包含的id先去掉,再把id排在最前面】
- Cookie的值就只有1個或2個,直接把id排在最前邊
if (bookHistory == null) {
return id;
}
//如果Cookie的值不是null的,那麼就分解Cookie的得到之前的id。
String[] strings = bookHistory.split("\\_");
//為了增刪容易並且還要判斷id是否存在於該字串內-----我們使用LinkedList集合裝載分解出來的id
List list = Arrays.asList(strings);
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addAll(list);
if (linkedList.contains(id)) {
linkedList.remove(id);
linkedList.addFirst(id);
}else {
if (linkedList.size() >= 3) {
linkedList.removeLast();
linkedList.addFirst(id);
} else {
linkedList.addFirst(id);
}
}
複製程式碼
- 這麼折騰完了,我們的Cookie值就在LinkedList集合裡邊了。接下來,我們要做的就是把集合中的值取出來,拼接成一個字串
StringBuffer stringBuffer = new StringBuffer();
//遍歷LinkedList集合,新增個下劃線“_”
for (String s : linkedList) {
stringBuffer.append(s + "_");
}
//最後一個元素後面就不需要下劃線了
return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
複製程式碼
- 好的,我們現在已經完成了Cookie值了。接下來設定Cookie的生命週期,回送給瀏覽器即可
String bookHistory = makeHistory(request, id);
Cookie cookie = new Cookie("bookHistory", bookHistory);
cookie.setMaxAge(30000);
response.addCookie(cookie);
複製程式碼
- 既然我們已經把Cookie回送給瀏覽器了。那麼接下來我們就在首頁上獲取Cookie的值,顯示使用者瀏覽過什麼商品就行了!
printWriter.write("您曾經瀏覽過的商品:");
printWriter.write("<br/>");
//顯示使用者瀏覽過的商品
Cookie[] cookies = request.getCookies();
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("bookHistory")) {
//獲取到的bookHistory是2_3_1之類的
String bookHistory = cookies[i].getValue();
//拆解成每一個id值
String[] ids = bookHistory.split("\\_");
//得到每一個id值
for (String id : ids) {
//通過id找到每一本書
Book book = linkedHashMap.get(id);
printWriter.write(book.getName());
printWriter.write("<br/>");
}
break;
}
}
複製程式碼
- 好的,我們來試驗一下吧!!,第一次訪問首頁,並沒有瀏覽過的商品
- 當我點選javaweb書籍再訪問首頁的時候
- 再點選ajax然後訪問首頁
- 再點選javaweb然後訪問首頁
- 點選oracle然後訪問首頁
- 好的,經過測試,該程式應該沒有什麼問題了!
涵蓋Java後端所有知識點的開源專案(已有5.8K star):github.com/ZhongFuChen…
如果大家想要實時關注我更新的文章以及分享的乾貨的話,微信搜尋Java3y。
PDF文件的內容均為手打,有任何的不懂都可以直接來問我(公眾號有我的聯絡方式)。