Servlet第五篇【介紹會話技術(shù)、Cookie的API、詳解、應(yīng)用】

什么是會話技術(shù)

基本概念: 指用戶開一個瀏覽器,訪問一個網(wǎng)站,只要不關(guān)閉該瀏覽器,不管該用戶點擊多少個超鏈接,訪問多少資源,直到用戶關(guān)閉瀏覽器,整個這個過程我們稱為一次會話.


為什么我們要使用會話技術(shù)?

會話跟蹤技術(shù)可以解決我們很多很多問題。

  • 在論壇登陸的時候,很多時候會有一個小框框問你是否要自動登陸,當你下次登陸的時候就不用輸入密碼了
image
  • 根據(jù)我以前瀏覽過的商品,猜我喜歡什么商品
image

Cookie

會話跟蹤技術(shù)有Cookie和Session,Cookie技術(shù)是先出現(xiàn)的。我們先講Cookie技術(shù)吧。

什么是Cookie

Cookie是由W3C組織提出,最早由netscape社區(qū)發(fā)展的一種機制

  • 網(wǎng)頁之間的交互是通過HTTP協(xié)議傳輸數(shù)據(jù)的,而Http協(xié)議是無狀態(tài)的協(xié)議。無狀態(tài)的協(xié)議是什么意思呢?一旦數(shù)據(jù)提交完后,瀏覽器和服務(wù)器的連接就會關(guān)閉,再次交互的時候需要重新建立新的連接。
  • 服務(wù)器無法確認用戶的信息,于是乎,W3C就提出了:給每一個用戶都發(fā)一個通行證,無論誰訪問的時候都需要攜帶通行證,這樣服務(wù)器就可以從通行證上確認用戶的信息。通行證就是Cookie
image

Cookie的流程:瀏覽器訪問服務(wù)器,如果服務(wù)器需要記錄該用戶的狀態(tài),就使用response向瀏覽器發(fā)送一個Cookie,瀏覽器會把Cookie保存起來。當瀏覽器再次訪問服務(wù)器的時候,瀏覽器會把請求的網(wǎng)址連同Cookie一同交給服務(wù)器。

Cookie API

  • Cookie類用于創(chuàng)建一個Cookie對象
  • response接口中定義了一個addCookie方法,它用于在其響應(yīng)頭中增加一個相應(yīng)的Set-Cookie頭字段
  • request接口中定義了一個getCookies方法,它用于獲取客戶端提交的Cookie

常用的Cookie方法:

  • public Cookie(String name,String value)
  • setValue與getValue方法
  • setMaxAge與getMaxAge方法
  • setPath與getPath方法
  • setDomain與getDomain方法
  • getName方法

簡單使用Cookie

  • 創(chuàng)建Cookie對象,發(fā)送Cookie給瀏覽器、

        //設(shè)置response的編碼
        response.setContentType("text/html;charset=UTF-8");

        //創(chuàng)建Cookie對象,指定名稱和值
        Cookie cookie = new Cookie("username", "zhongfucheng");

        //向瀏覽器給一個Cookie
        response.addCookie(cookie);

        response.getWriter().write("我已經(jīng)向瀏覽器發(fā)送了一個Cookie");

  • 瀏覽器本身沒有任何Cookie
image
  • 訪問Servlet1,再回到文件夾中,還是沒有發(fā)現(xiàn)Cookie,這是為什么呢?我明明向瀏覽器發(fā)送了一個Cookie的
  • 原來發(fā)送Cookie給瀏覽器是需要設(shè)置Cookie的時間的。在給瀏覽器之前,設(shè)置一下Cookie的時間

        //設(shè)置Cookie的時間
        cookie.setMaxAge(1000);
  • 再次訪問,已經(jīng)發(fā)現(xiàn)文件夾中多了個Cookie的文本了
image

Cookie細節(jié)

Cookie不可跨域名性

  • 很多人在初學(xué)的時候可能有一個疑問:在訪問Servlet的時候瀏覽器是不是把所有的Cookie都帶過去給服務(wù)器,會不會修改了別的網(wǎng)站的Cookie
  • 答案是否定的。Cookie具有不可跨域名性。瀏覽器判斷一個網(wǎng)站是否能操作另一個網(wǎng)站的Cookie的依據(jù)是域名。所以一般來說,當我訪問baidu的時候,瀏覽器只會把baidu頒發(fā)的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("我頒發(fā)了一個Cookie,值保存的是中文數(shù)據(jù)");
  • 訪問Servlet1,好吧。出異常了!
image
  • 中文屬于Unicode字符,英文數(shù)據(jù)ASCII字符,中文占4個字符或者3個字符,英文占2個字符。
  • 解決:Cookie使用Unicode字符時需要對Unicode字符進行編碼。

        //對Unicode字符進行編碼
        Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
  • 再次訪問Servlet1,已經(jīng)把Cookie成功頒發(fā)給瀏覽器了
image
image
  • 我們發(fā)現(xiàn)Cookie保存在硬盤的中文數(shù)據(jù)是經(jīng)過編碼的,那么我們在取出Cookie的時候要對中文數(shù)據(jù)進行解碼

        Cookie[] cookies = request.getCookies();
        for (int i = 0; cookies != null && i < cookies.length; i++) {
            String name = cookies[i].getName();

            //經(jīng)過URLEncoding就要URLDecoding
            String value = URLDecoder.decode(cookies[i].getValue(), "UTF-8");

            printWriter.write(name + "------" + value);
        }
  • 取出存進Cookie的值
image

Cookie的有效期

Cookie的有效期是通過setMaxAge()來設(shè)置的。

  • 如果MaxAge為正數(shù),瀏覽器會把Cookie寫到硬盤中,只要還在MaxAge秒之前,登陸網(wǎng)站時該Cookie就有效【不論關(guān)閉了瀏覽器還是電腦】
  • 如果MaxAge為負數(shù),Cookie是臨時性的,僅在本瀏覽器內(nèi)有效,關(guān)閉瀏覽器Cookie就失效了,Cookie不會寫到硬盤中。Cookie默認值就是-1。這也就為什么在我第一個例子中,如果我沒設(shè)置Cookie的有效期,在硬盤中就找不到對應(yīng)的文件。
  • 如果MaxAge為0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie對應(yīng)的方法,把MaxAge設(shè)置為0等同于刪除Cookie

Cookie的修改和刪除

  • 上面我們已經(jīng)知道了Cookie機制沒有提供刪除Cookie的方法。其實細心點我們可以發(fā)現(xiàn),Cookie機制也沒有提供修改Cookie的方法。那么我們怎么修改Cookie的值呢
  • Cookie存儲的方式類似于Map集合,如下圖所示
image
  • Cookie的名稱相同,通過response添加到瀏覽器中,會覆蓋原來的Cookie。

  • 以country為名保存的是%E4%B8%AD%E5%9B%BD,下面我再以country為名,把值改變一下。

    image


        String name = "看完博客就點贊";

        //對Unicode字符進行編碼
        Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
  • 再次查看Cookie的時候,值已經(jīng)改變了,但是文件還是那一份
image
  • 現(xiàn)在我要刪除該Cookie,把MaxAge設(shè)置為0,并添加到瀏覽器中即可

        String name = "看完博客就點贊";

        //對Unicode字符進行編碼
        Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));

        //一定不要忘記添加到瀏覽器中
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        printWriter.write("我刪除了該Cookie");
  • 訪問Servlet,在硬盤已經(jīng)找不到Cookie的文件了!
image
image
  • 注意:刪除,修改Cookie時,新建的Cookie除了value、maxAge之外的所有屬性都要與原Cookie相同。否則瀏覽器將視為不同的Cookie,不予覆蓋,導(dǎo)致刪除修改失敗

  • 我們來試驗一下把。


        String name = "看完博客就點贊";

        //對Unicode字符進行編碼
        Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));

        //一定不要忘記添加到瀏覽器中
        cookie.setMaxAge(10000);
        response.addCookie(cookie);
image
  • 上面新建了一個Cookie,我修改下Cookie的其他屬性,再刪除,看能否把Cookie刪除掉

        //一定不要忘記添加到瀏覽器中

        cookie.setPath("/ouzicheng");
        cookie.setMaxAge(0);
        response.addCookie(cookie);

        printWriter.write("刪除一個Cookie");
  • 結(jié)果Cookie還在硬盤中
image

Cookie的域名

Cookie的domain屬性決定運行訪問Cookie的域名。domain的值規(guī)定為“.域名”

image
image

        Cookie cookie = new Cookie("name", "zhongfucheng");
        cookie.setMaxAge(1000);
        response.addCookie(cookie);

        printWriter.write("使用www.zhongfucheng.com域名添加了一個Cookie");
image
  • 首先,證明了Cookie不可跨名性,localhost域名拿不到www.zhongfucheng.com頒發(fā)給瀏覽器的Cookie
image
  • 再使用www.image.zhongfucheng.com域名訪問,證明即使一級域名相同,二級域名不同,也不能獲取到Cookie
image
  • 當然,使用www.zhongfucheng.com當然能獲取到Cookie,Cookie通過請求頭帶給服務(wù)器
image

        Cookie cookie = new Cookie("name", "ouzicheng");
        cookie.setMaxAge(1000);
        cookie.setDomain(".zhongfucheng.com");
        response.addCookie(cookie);

        printWriter.write("使用www.zhongfucheng.com域名添加了一個Cookie,只要一級是zhongfucheng.com即可訪問");
image

Cookie的路徑

Cookie的path屬性決定允許訪問Cookie的路徑

  • 一般地,Cookie發(fā)布出來,整個網(wǎng)頁的資源都可以使用?,F(xiàn)在我只想Servlet1可以獲取到Cookie,其他的資源不能獲取。

  • 使用Servlet2頒發(fā)一個Cookie給瀏覽器,設(shè)置路徑為"/Servlet1"。


        Cookie cookie = new Cookie("username", "java");
        cookie.setPath("/Servlet1");
        cookie.setMaxAge(1000);
        response.addCookie(cookie);

        printWriter.write("該Cookie只有Servlet1獲取得到");

  • 使用Servlet3訪問服務(wù)器,看看瀏覽器是否把Cookie帶上。顯然,瀏覽器訪問Servlet3并沒有把Cookie帶上。
image
  • 使用Servlet1訪問服務(wù)器,看看瀏覽器是否把Cookie帶上。答案是肯定的!
image

Cookie的安全屬性

  • HTTP協(xié)議不僅僅是無狀態(tài)的,而且是不安全的!如果不希望Cookie在非安全協(xié)議中傳輸,可以設(shè)置Cookie的secure屬性為true,瀏覽器只會在HTTPS和SSL等安全協(xié)議中傳輸該Cookie
  • 當然了,設(shè)置secure屬性不會將Cookie的內(nèi)容加密。如果想要保證安全,最好使用md5算法加密【后面有】。

Cookie的應(yīng)用

顯示用戶上次訪問的時間

  • 其實就是每次登陸的時候,取到Cookie保存的值,再更新下Cookie的值

  • 訪問Serlvet有兩種情況

  • 第一次訪問

  • 已經(jīng)訪問過了

  • 全部代碼如下:


        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = response.getWriter();

        //獲取網(wǎng)頁上所有的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]);

                //既然已經(jīng)找到了就可以break循環(huán)了
                break;
            }
        }

        //如果Cookie的值是空的,那么就是第一次訪問
        if (cookieValue == null) {
            //創(chuàng)建一個Cookie對象,日期為當前時間
            Cookie cookie = new Cookie("time", simpleDateFormat.format(new Date()));

            //設(shè)置Cookie的生命期
            cookie.setMaxAge(20000);

            //response對象回送Cookie給瀏覽器
            response.addCookie(cookie);

            printWriter.write("您是第一次登陸?。?);
        }
  • 按照正常的邏輯來寫,程序流程應(yīng)該是這樣子的。先創(chuàng)建Cookie對象,回送Cookie給瀏覽器。再遍歷Cookie,更新Cookie的值。
image
  • 但是,按照上面的邏輯是做不到的!因為每次訪問Servlet的時候都會覆蓋原來的Cookie,取到Cookie的值永遠都是當前時間,而不是上次保存的時間。

  • 我們換一個邏輯寫:先檢查(遍歷)所有Cookie有沒有我要的,如果得不到我想要的Cookie,Cookie的值是null,那么就是第一次登陸,于是就有了上面的代碼了。

  • 我們來看下效果吧!當我第一次登陸的時候

image
  • Cookie保存在硬盤中。
image
  • 再次訪問Servlet。明顯地,取到的就是Cookie的值
image

顯示上次瀏覽過商品

  • 我就以書籍為例子了!首先設(shè)計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方法
  • 設(shè)計一個簡單的數(shù)據(jù)庫存儲數(shù)據(jù)。就用LinkedHashMap集合【根據(jù)商品的id找書籍所以用Map,刪改較多所以用Linked】

    private static LinkedHashMap<String, Book> linkedHashMap = new LinkedHashMap();

    //簡化開發(fā)復(fù)雜度,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;
    }

  • 顯示網(wǎng)頁上所有的書籍【首頁】

        printWriter.write("網(wǎng)頁上所有的書籍:"+"<br/>");

        //拿到數(shù)據(jù)庫所有的書
        LinkedHashMap<String, Book> linkedHashMap = DB.getAll();
        Set<Map.Entry<String, Book>> entry = linkedHashMap.entrySet();

        //顯示所有的書到網(wǎng)頁上
        for (Map.Entry<String, Book> stringBookEntry : entry) {
            Book book = stringBookEntry.getValue();
            printWriter.write(book.getId() +"           "+ book.getName()+"<br/>");
        }

image
  • 接著,我們要做的就是給顯示的書籍掛上一個超鏈接,當用戶點擊想看的書籍時,就跳轉(zhuǎn)到該書籍的詳細信息頁面
  • 超鏈接應(yīng)該把書的id傳遞過去,不然處理頁面是不知道用戶想看的是哪一本書的!

        //顯示所有的書到網(wǎng)頁上
        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/>");
        }
image
  • 接收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/>");
  • 點擊想要的書籍。
image
  • 得到書籍的詳細信息
image
  • 既然用戶點擊了書籍,那么服務(wù)器就應(yīng)該頒發(fā)Cookie給瀏覽器,記住用戶點擊了該書籍

  • 現(xiàn)在問題來了,Cookie的值應(yīng)該是什么呢?試想一下,待會還要把瀏覽過的書籍顯示出來,所以用書籍的id是最好不過的。想到了用書籍的id作為Cookie的值,我們還要定義一些規(guī)則!

  • 我們可能有非常多的書籍,不可能把用戶瀏覽過的書籍都顯示出來。所以我們定義只能顯示3本瀏覽過的書籍

  • 書籍的id都是數(shù)字,如果不做任何修改,存到Cookie里邊可能就是231,345,123此類的數(shù)字,這樣取出某一個id的時候就十分費勁并且后面還要判斷該書是否存在Cookie里邊了,所以我們要把存儲到Cookie的書籍id分割起來。所以我們定義”_“作為分隔符

  • 按上面的應(yīng)用,我們的邏輯應(yīng)該是:先遍歷下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的值也分幾種情況

    1. Cookie的值為null【直接把傳入進來的id當做是Cookie的值】
    2. Cookie的值長度有3個了【把排在最后的id去掉,把傳進來的id排在最前邊】
    3. Cookie的值已經(jīng)包含有傳遞進來的id了【把已經(jīng)包含的id先去掉,再把id排在最前面】
    4. Cookie的值就只有1個或2個,直接把id排在最前邊

        if (bookHistory == null) {
            return id;
        }

        //如果Cookie的值不是null的,那么就分解Cookie的得到之前的id。
        String[] strings = bookHistory.split("\\_");

        //為了增刪容易并且還要判斷id是否存在于該字符串內(nèi)-----我們使用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();

  • 好的,我們現(xiàn)在已經(jīng)完成了Cookie值了。接下來設(shè)置Cookie的生命周期,回送給瀏覽器即可

        String bookHistory = makeHistory(request, id);
        Cookie cookie = new Cookie("bookHistory", bookHistory);
        cookie.setMaxAge(30000);
        response.addCookie(cookie);
  • 既然我們已經(jīng)把Cookie回送給瀏覽器了。那么接下來我們就在首頁上獲取Cookie的值,顯示用戶瀏覽過什么商品就行了!

        printWriter.write("您曾經(jīng)瀏覽過的商品:");
        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;
            }

        }
  • 好的,我們來試驗一下吧??!,第一次訪問首頁,并沒有瀏覽過的商品
image
  • 當我點擊javaweb書籍再訪問首頁的時候
image
  • 再點擊ajax然后訪問首頁
image
  • 再點擊javaweb然后訪問首頁
image
  • 點擊oracle然后訪問首頁
image
  • 好的,經(jīng)過測試,該程序應(yīng)該沒有什么問題了!

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術(shù)文章的同學(xué),可以關(guān)注微信公眾號:Java3y

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容