官术网_书友最值得收藏!

  • Java Web程序設(shè)計(jì)
  • 張磊 丁香乾編著
  • 553字
  • 2018-12-29 18:50:49

第2章 Servlet會(huì)話跟蹤

本章目標(biāo)

■ 掌握會(huì)話跟蹤的相關(guān)技術(shù)

■ 理解Cookie的原理

■ 掌握Cookie的讀寫(xiě)方法使用

■ 理解Session的原理

■ 理解Session的生命周期

■ 熟練掌握Session的方法使用

■ 掌握ServletContext的方法使用

學(xué)習(xí)導(dǎo)航

任務(wù)描述

【描述2.D.1】

使用Cookie將用戶的登錄信息保存到客戶端,當(dāng)用戶再次登錄時(shí)在相應(yīng)的文本欄中顯示上次登錄時(shí)輸入的信息。

【描述2.D.2】

用戶通過(guò)表單提交數(shù)據(jù),使用Session在該用戶訪問(wèn)的所有頁(yè)面中輸出此數(shù)據(jù)。

【描述2.D.3】

基于任務(wù)描述2.D.2,通過(guò)URL重寫(xiě)來(lái)實(shí)現(xiàn)Session會(huì)話技術(shù)。

【描述2.D.4】

使用ServletContext顯示在線訪問(wèn)人數(shù)。

【描述2.D.5】

編寫(xiě)一個(gè)Servlet,訪問(wèn)web.xml中的初始化參數(shù)信息。

2.1 會(huì)話跟蹤簡(jiǎn)介

HTTP是一種無(wú)狀態(tài)的協(xié)議,這就意味著Web服務(wù)器并不了解同一用戶以前請(qǐng)求的信息,即當(dāng)瀏覽器與服務(wù)器之間的請(qǐng)求、響應(yīng)結(jié)束后,服務(wù)器上不會(huì)保留任何客戶端的信息。但對(duì)于現(xiàn)在的Web應(yīng)用而言,往往需要記錄特定客戶端的一系列請(qǐng)求之間的聯(lián)系,以便于對(duì)客戶的狀態(tài)進(jìn)行追蹤。比如,在購(gòu)物網(wǎng)站,服務(wù)器會(huì)為每個(gè)客戶配置一個(gè)購(gòu)物車,購(gòu)物車需要一直跟隨客戶,以便于客戶將商品放入購(gòu)物車中,而且每個(gè)客戶之間的購(gòu)物車也不會(huì)混淆。這就是本章所講到的會(huì)話跟蹤技術(shù)。

會(huì)話跟蹤技術(shù)的方案包括以下幾種:

■ Cookie技術(shù)

■ Session技術(shù)

■ URL重寫(xiě)技術(shù)

■ 隱藏表單域技術(shù)

注意 由于隱藏表單域技術(shù)是將會(huì)話ID添加到隱藏域中,實(shí)現(xiàn)起來(lái)較為煩瑣,因此在實(shí)際應(yīng)用中不推薦使用該技術(shù),本章也不做講解。

2.2 Cookie

Cookie是服務(wù)器發(fā)給客戶端的一小段文本,保存在瀏覽器所在客戶端的內(nèi)存或磁盤(pán)上。服務(wù)器可以從客戶端讀出這些Cookie。通過(guò)Cookie,客戶端和服務(wù)器端可建立起一種聯(lián)系,也就是說(shuō),Cookie是一種可以讓服務(wù)器對(duì)客戶端信息進(jìn)行保存和獲取的機(jī)制,從而大大擴(kuò)展了基于Web的應(yīng)用功能。

Cookie是會(huì)話跟蹤的一種解決方案,例如在需要登錄的網(wǎng)站,用戶第一次輸入用戶名和密碼后,可以將其利用Cookie保存在客戶端,當(dāng)用戶下一次訪問(wèn)這個(gè)網(wǎng)站的時(shí)候,就能直接從客戶端讀出該用戶名和密碼來(lái),用戶就不需要每次都重新登錄。另外,也可以根據(jù)需要讓用戶定制自己喜歡的內(nèi)容,用戶可以選擇自己喜歡的新聞、顯示的風(fēng)格、顯示的順序等,這些相關(guān)的設(shè)置信息都保存到客戶端的Cookie中,當(dāng)用戶每次訪問(wèn)該網(wǎng)站時(shí),就可以按照他預(yù)設(shè)的內(nèi)容進(jìn)行顯示。

當(dāng)然,因?yàn)镃ookie需要將信息保存在客戶端的計(jì)算機(jī)上,所以,從Cookie誕生之日起,有關(guān)于它所可能帶來(lái)的安全問(wèn)題就一直是人們所關(guān)注的焦點(diǎn)。但截至目前,還沒(méi)有因?yàn)镃ookie所帶來(lái)的重大安全問(wèn)題,這主要也是由Cookie的安全機(jī)制所決定的:

■ Cookie不會(huì)以任何方式在客戶端被執(zhí)行。

■ 瀏覽器會(huì)限制來(lái)自同一個(gè)網(wǎng)站的Cookie數(shù)目。

■ 單個(gè)Cookie的長(zhǎng)度是有限制的。

■ 瀏覽器限制了最多可以接受的Cookie數(shù)目。

基于這些安全機(jī)制,客戶端就不必?fù)?dān)心硬盤(pán)被這些Cookie占用太大的空間。雖然Cookie不太可能帶來(lái)安全問(wèn)題,但可能會(huì)帶來(lái)一些隱私問(wèn)題,通常情況下,不要將敏感的信息保存到Cookie中,特別是一些重要的個(gè)人資料,如信用卡賬號(hào)、密碼等。另外,瀏覽器可以設(shè)置成拒絕Cookie,因此Web開(kāi)發(fā)中不要使程序過(guò)度依賴于Cookie,因?yàn)橐坏┯脩絷P(guān)閉了瀏覽器的Cookie功能,就可能造成程序無(wú)法正確運(yùn)行。

2.2.1 Cookie的創(chuàng)建及使用

通過(guò)Cookie類的構(gòu)造方法可以創(chuàng)建該類的實(shí)例。Cookie的構(gòu)造方法帶有兩個(gè)String類型的參數(shù),分別用于指定Cookie的屬性名稱和屬性值,例如:

    Cookie userCookie = new Cookie("uName",username);

Cookie類提供了一些方法,常用方法如表2-1所示。

表2-1 Cookie類常用方法

創(chuàng)建完成的Cookie對(duì)象,可以使用HttpServletResponse的addCookie()方法將其發(fā)送到客戶端。addCookie()方法接收一個(gè)Cookie類型的值,例如:

    // 將userCookie發(fā)送到客戶端
    response.addCookie(userCookie);

使用HttpServletRequest的getCookies()方法可以從客戶端獲得這個(gè)網(wǎng)站的所有Cookie,該方法返回一個(gè)包含本站所有Cookie的數(shù)組,遍歷該數(shù)組可以獲得對(duì)應(yīng)的Cookie,例如:

    Cookie[] cookies = request.getCookies();

默認(rèn)情況下,Cookie在客戶端是保存在內(nèi)存中的,如果瀏覽器關(guān)閉,Cookie也就失效了。如果想要讓Cookie長(zhǎng)久地保存在磁盤(pán)上,通過(guò)使用表2-1中的setMaxAge()方法設(shè)置其過(guò)期時(shí)間,如將客戶端的Cookie的過(guò)期時(shí)間設(shè)置為1周,其示例代碼如下:

    // 在客戶端保存一周
    userCookie.setMaxAge(7*24*60*60);

注意 Cookie的保存位置在不同的操作系統(tǒng)下是不同的,其中Windows XP系統(tǒng)下其保存位置是在C:\Documents and Settings\當(dāng)前系統(tǒng)用戶名\Local Settings\temporary internet files文件夾中。

2.2.2 Cookie示例

下述代碼用于實(shí)現(xiàn)任務(wù)描述2.D.1,使用Cookie保存用戶名和密碼,當(dāng)用戶再次登錄時(shí)在相應(yīng)的文本框中顯示上次登錄時(shí)輸入的信息。(1)編寫(xiě)用于接收用戶輸入的HTML表單文件,在該例子中,沒(méi)有使用HTML文件而是使用一個(gè)Servlet來(lái)完成此功能,這是因?yàn)樾枰ㄟ^(guò)Servlet去讀取客戶端的Cookie,而HTML文件無(wú)法完成此功能。

【描述2.D.1】 LoginServlet.java

    public class LoginServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            String cookieName = "userName";
            String cookiePwd = "pwd";
            // 獲得所有cookie
            Cookie[] cookies = request.getCookies();
            String userName = "";
            String pwd = "";
            String isChecked = "";
            // 如果cookie數(shù)組不為null,說(shuō)明曾經(jīng)設(shè)置過(guò)
            // 也就是曾經(jīng)登錄過(guò),那么取出上次登錄的用戶名、密碼
            if (cookies != null) {
                // 如果曾經(jīng)設(shè)置過(guò)cookie,checkbox狀態(tài)應(yīng)該是checked
                isChecked = "checked";
                for (int i = 0; i < cookies.length; i++) {
                    // 取出登錄名
                    if (cookies[i].getName().equals(cookieName)) {
                        userName = cookies[i].getValue();
                    }
                    // 取出密碼
                    if (cookies[i].getName().equals(cookiePwd)) {
                        pwd = cookies[i].getValue();
                    }
                }
            }
            response.setContentType("text/html;charset=GBK");
            PrintWriter out = response.getWriter();
            out.println("<html>\n");
            out.println("<head><title>登錄</title></head>\n");
            out.println("<body>\n");
            out.println("<center>\n");
            out.println("    <form action='CookieTest'" + " method='post'>\n");
            out.println("姓名:<input type='text'" + " name='UserName' value='"
                    + userName + "'><br/>\n");
            out.println("密碼:<input type='password' name='Pwd' value='" + pwd
                    + "'><br/>\n");
            out.println("保存用戶名和密碼<input type='checkbox'"
                    + "name='SaveCookie' value='Yes' " + isChecked + ">\n");
            out.println("         <br/>\n");
            out.println("         <input type=\"submit\">\n");
            out.println("    </form>\n");
            out.println("</center>\n");
            out.println("</body>\n");
            out.println("</html>\n");
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }

上述代碼中,首先使用request.getCookies()獲取客戶端cookies數(shù)組;再遍歷該數(shù)組,找到對(duì)應(yīng)的Cookie,取出用戶名和密碼;最后將信息顯示在相應(yīng)的表單控件中。

(2)編寫(xiě)CookieTest.java程序。

【描述2.D.1】 CookieTest.java

    public class CookieTest extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            Cookie userCookie = new Cookie("userName", request
                    .getParameter("UserName"));
            Cookie pwdCookie = new Cookie("pwd", request.getParameter("Pwd"));
            if (request.getParameter("SaveCookie") != null
                    && request.getParameter("SaveCookie").equals("Yes")) {
                userCookie.setMaxAge(7 * 24 * 60 * 60);
                pwdCookie.setMaxAge(7 * 24 * 60 * 60);
            } else {
                // 刪除客戶端對(duì)應(yīng)的Cookie
                userCookie.setMaxAge(0);
                pwdCookie.setMaxAge(0);
            }
            response.addCookie(userCookie);
            response.addCookie(pwdCookie);
            PrintWriter out = response.getWriter();
            out.println("Welcome," + request.getParameter("UserName"));
        }

        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }

上述代碼中,首先創(chuàng)建兩個(gè)Cookie對(duì)象,分別用來(lái)儲(chǔ)存表單中傳遞過(guò)來(lái)的登錄名和密碼,然后根據(jù)客戶端的“SaveCookie”元素的值,決定是否向客戶端發(fā)送Cookie,或者刪除以前存儲(chǔ)的Cookie。

啟動(dòng)Tomcat,在IE中訪問(wèn)http://localhost:8080/ch02/LoginServlet,運(yùn)行結(jié)果如圖2-1所示。

輸入姓名和密碼,選中相應(yīng)的保存復(fù)選框,單擊提交按鈕,顯示結(jié)果如圖2-2所示。

圖2-1 第一次訪問(wèn)LoginServlet

圖2-2 CookieTest結(jié)果

當(dāng)再次登錄時(shí),用戶名和密碼已顯示,如圖2-3所示。

圖2-3 再次訪問(wèn)LoginServlet

2.3 Session

使用Cookie可以將請(qǐng)求的狀態(tài)信息傳遞到下一次請(qǐng)求中(如任務(wù)描述2.D.1),但是如果傳遞的狀態(tài)信息較多,將極大地降低網(wǎng)絡(luò)傳輸效率,并且會(huì)增大服務(wù)器端程序的處理難度,為此各種服務(wù)器端技術(shù)都提供了一種將會(huì)話狀態(tài)保存在服務(wù)器端的方案,即Session(會(huì)話)技術(shù)。

Session是在Java Servlet API中引入的一個(gè)非常重要的機(jī)制,用于跟蹤客戶端的狀態(tài),即在一段時(shí)間內(nèi),單個(gè)客戶端與Web服務(wù)器之間的一連串的交互過(guò)程稱為一個(gè)會(huì)話。

HttpSession是Java Servlet API中提供的對(duì)Session機(jī)制的實(shí)現(xiàn)規(guī)范,它僅僅是個(gè)接口,Servlet容器必須實(shí)現(xiàn)這個(gè)接口。當(dāng)一個(gè)Session開(kāi)始時(shí),Servlet容器會(huì)創(chuàng)建一個(gè)HttpSession對(duì)象,并同時(shí)在內(nèi)存中為其開(kāi)辟一個(gè)空間,用來(lái)存放此Session對(duì)應(yīng)的狀態(tài)信息。Servlet容器為每一個(gè)HttpSession對(duì)象分配一個(gè)唯一的標(biāo)識(shí)符,稱為SessionID,同時(shí)將SessionID發(fā)送到客戶端,由瀏覽器負(fù)責(zé)保存此SessionID。這樣,當(dāng)客戶端再發(fā)送請(qǐng)求時(shí),瀏覽器會(huì)同時(shí)發(fā)送SessionID,Servlet容器可以從請(qǐng)求對(duì)象中讀取SessionID,根據(jù)SessionID的值找到相應(yīng)的HttpSession對(duì)象。每個(gè)客戶端對(duì)應(yīng)于服務(wù)器端的一個(gè)HttpSession對(duì)象,這通過(guò)SessionID區(qū)分。Session機(jī)制如圖2-4所示。

圖2-4 Session機(jī)制

注意 通常服務(wù)器借助于Cookie把SessionID存儲(chǔ)在瀏覽器進(jìn)程中,在該瀏覽器進(jìn)程下一次訪問(wèn)服務(wù)器時(shí),服務(wù)器就可以從請(qǐng)求中的Cookie里獲取SessionID。此外,Session還可以借助URL重寫(xiě)的方式在客戶端保存SessionID。

2.3.1 Session創(chuàng)建

Servlet容器根據(jù)HttpServletRequest對(duì)象中提供的SessionID,可以找到對(duì)應(yīng)的HttpSession對(duì)象。在HttpServletRequest中提供了以下兩種方法來(lái)獲取HttpSession:

■ getSession()方法取得請(qǐng)求所在的會(huì)話,如果該會(huì)話對(duì)象不存在則創(chuàng)建一個(gè)新會(huì)話。

■ getSession(boolean create)返回當(dāng)前請(qǐng)求的會(huì)話。如果當(dāng)前請(qǐng)求不屬于任何會(huì)話,而且create參數(shù)為true,則創(chuàng)建一個(gè)會(huì)話,否則返回null。此后所有來(lái)自同一個(gè)對(duì)象的請(qǐng)求都屬于這個(gè)會(huì)話,通過(guò)它的getSession(false)返回的是當(dāng)前會(huì)話。

例如,可以使用下面兩種方式獲取當(dāng)前Session:

    HttpSession session=request.getSession();       //獲取當(dāng)前Session

    HttpSession session=request.getSession(true);  //獲取當(dāng)前Session

2.3.2 Session使用

HttpSession中定義了如表2-2所示的方法。

表2-2 HttpSession常用方法

其中,用于存取數(shù)據(jù)的方法有以下兩個(gè)。

setAttribute():用于在Session對(duì)象中保存數(shù)據(jù),數(shù)據(jù)以Key/Value映射形式存放。

getAttribute():從Session中提取指定Key對(duì)應(yīng)的Value值。

向Session對(duì)象中保存數(shù)據(jù)的示例代碼如下:

    // 將username保存到Session中,并指定其引用名稱為uName
    session.setAttribute("uName", username);

從Session中提取存放的信息,則代碼如下:

    // 取出數(shù)據(jù),注意:因該方法的返回?cái)?shù)據(jù)類型為Object,所以需要轉(zhuǎn)換數(shù)據(jù)類型
    String username = (String)session.getAttribute("uName");

用于銷毀Session的方法如下。

invalidate():調(diào)用此方法可以同時(shí)刪除HttpSession對(duì)象和數(shù)據(jù)。

使用invalidate()銷毀Session的示例代碼如下:

    // 銷毀Session(常用于用戶注銷)
    session.invalidate();

注意 有3種情況可以結(jié)束Session:

(1)關(guān)閉瀏覽器,Session關(guān)閉。

(2)調(diào)用HttpSession的invalidate()方法。

(3)兩次訪問(wèn)時(shí)間間隔大于Session定義的非活動(dòng)時(shí)間間隔。

2.3.3 Session生命周期

Session生命周期經(jīng)過(guò)以下幾個(gè)過(guò)程:

01 客戶端向服務(wù)器第一次發(fā)送請(qǐng)求的時(shí)候,request中并無(wú)SessionID。

02 此時(shí)服務(wù)器會(huì)創(chuàng)建一個(gè)Session對(duì)象,并分配一個(gè)SessionID。Session對(duì)象保存在服務(wù)器端,此時(shí)為新建狀態(tài),調(diào)用session.isNew()返回true。

03 當(dāng)服務(wù)器端處理完畢后,會(huì)將SessionID通過(guò)response對(duì)象傳回到客戶端,瀏覽器負(fù)責(zé)保存到當(dāng)前進(jìn)程中。

04 當(dāng)客戶端再次發(fā)送請(qǐng)求時(shí),會(huì)同時(shí)將SessionID發(fā)送給服務(wù)器。

05 服務(wù)器根據(jù)傳遞過(guò)來(lái)的SessionID將這次請(qǐng)求(request)與保存在服務(wù)器端的Session對(duì)象聯(lián)系起來(lái)。此時(shí)Session已不處于新建狀態(tài),調(diào)用session.isNew()返回false。

06 循環(huán)執(zhí)行過(guò)程3~5,直到Session超時(shí)或銷毀。

Session的生命周期和訪問(wèn)范圍如圖2-5所示。

圖2-5 Session訪問(wèn)范圍

在圖2-5中,每個(gè)客戶(如Client1)可以訪問(wèn)多個(gè)Servlet,但是一個(gè)客戶的多個(gè)請(qǐng)求將共享一個(gè)Session,同一Web應(yīng)用下的所有Servlet共享一個(gè)ServletContext,即Servlet上下文。ServletContext在本章2.5節(jié)將詳細(xì)介紹。

2.3.4 Session演示

下述代碼完成任務(wù)描述2.D.2,用戶在初始化頁(yè)面中輸入一個(gè)值,單擊“提交”按鈕,會(huì)進(jìn)入第一個(gè)Servlet;第一個(gè)Servlet將輸入的值分別保存到request和Session中;在第二個(gè)Servlet中從request和Session對(duì)象中提取信息并顯示。(1)編寫(xiě)session.html頁(yè)面,代碼如下所示。

【描述2.D.2】session.html

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=GBK">
    <title>Session示例</title>
    </head>
    <body>
    <center>
    <form method="POST" action="s1">
    <table>
        <tr>
            <td>輸入數(shù)據(jù): <input type="text" name="count"></td>
        </tr>
    </table>
    <center><input type="submit" value="提交"></center>
    </form>
    </center>
    </body>
    </html>

在此頁(yè)面中,將表單信息提交給第一個(gè)名為FirstServlet的Servlet處理。因?yàn)榈谝粋€(gè)Servlet的<url-pattern>是“/s1”,因此表單的action屬性值為“s1”。

(2)編寫(xiě)第一個(gè)Servlet,代碼如下所示。

【描述2.D.2】FirstServlet.java

    public class FirstServlet extends HttpServlet {
        public FirstServlet() {
            super();
        }
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            // 設(shè)置請(qǐng)求的編碼字符為GBK
            request.setCharacterEncoding("GBK");
            // 設(shè)置響應(yīng)的文本類型為html,編碼字符為GBK
            response.setContentType("text/html; charset=GBK");
            PrintWriter out = response.getWriter();
            // 獲取表單數(shù)據(jù)
            String str = request.getParameter("count");
            request.setAttribute("request_param", str);
            HttpSession  session  =  request.getSession();
            session.setAttribute("session_param", str);
            out.println("<a href='s2'>下一頁(yè)</a>");
        }
    }

上述代碼中,首先提取表單數(shù)據(jù),并分別保存在request和session對(duì)象中。再通過(guò)下面語(yǔ)句在客戶端瀏覽器中顯示一個(gè)超鏈接,單擊此超鏈接,可以鏈接到第二個(gè)Servlet(第二個(gè)Servlet的<url-pattern>設(shè)置為“/s2”)。

    out.println("<a href='s2'>下一頁(yè)</a>")

(3)編寫(xiě)第二個(gè)Servlet,代碼如下所示。

【描述2.D.2】SecondServlet.java

    public class SecondServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            doPost(request, response);
        }
        protected void doPost(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            Object obj = request.getAttribute("request_param");
            String request_param = null;
            if (obj != null) {
                request_param = obj.toString();
            } else {
                request_param = "null";
            }
            HttpSession  session  =  request.getSession();
            Object obj2 = session.getAttribute("session_param");
            String session_param = null;
            if (obj2 != null) {
                session_param = obj2.toString();
            } else {
                session_param = "null";
            }
            response.setContentType("text/html; charset=GBK");
            PrintWriter out = response.getWriter();
            out.println("<html>");
            out.println("<body >");
            out.println("<h2>請(qǐng)求對(duì)象中的參數(shù)是 :" + request_param +
            "</h2>");
            out.println("<h2>Session對(duì)象中的參數(shù)是 :" + session_param
                    + "</h2></body></html>");
        }
    }

上述代碼中,分別從request和session對(duì)象中獲取數(shù)據(jù)并輸出。

啟動(dòng)Tomcat,在IE中訪問(wèn)http://localhost:8080/ch02/session.html,運(yùn)行結(jié)果如圖2-6所示。

在文本框中輸入數(shù)據(jù),單擊“提交”按鈕,提交給FirstServlet處理,運(yùn)行結(jié)果如圖2-7所示。

圖2-6 session.html

圖2-7 FirstServlet運(yùn)行結(jié)果

單擊“下一頁(yè)”超鏈接,進(jìn)入SecondServlet,運(yùn)行結(jié)果如圖2-8所示。

圖2-8 SecondServlet.java運(yùn)行結(jié)果

如圖2-8所示,保存在request對(duì)象中的數(shù)據(jù)變?yōu)閚ull,而保存在Session對(duì)象中的數(shù)據(jù)是正確的。因?yàn)樵趩螕簟跋乱豁?yè)”超鏈接后進(jìn)入第二個(gè)Servlet時(shí),上一次的request已經(jīng)結(jié)束,此時(shí)是一個(gè)新的請(qǐng)求,該request對(duì)象中并無(wú)保存數(shù)據(jù),因此提取的數(shù)據(jù)只能為null;而這兩次請(qǐng)求位于同一個(gè)會(huì)話中,Session的生命周期并未結(jié)束,因此能夠獲取Session中保存的數(shù)據(jù)。

在上述任務(wù)的執(zhí)行過(guò)程中,服務(wù)器在處理客戶端的請(qǐng)求時(shí)創(chuàng)建了新的HttpSession對(duì)象,將會(huì)話標(biāo)識(shí)號(hào)(SessionID)作為一個(gè)Cookie項(xiàng)加入到響應(yīng)信息中返回給客戶端。瀏覽器再次發(fā)送請(qǐng)求時(shí),服務(wù)器程序從Cookie中找到SessionID,就可以檢索到已經(jīng)為該客戶端創(chuàng)建了的HttpSession對(duì)象,而不必再創(chuàng)建新的對(duì)象,通過(guò)這種方式就實(shí)現(xiàn)了對(duì)同一個(gè)客戶端的會(huì)話狀態(tài)跟蹤。

注意 使用Cookie實(shí)現(xiàn)Session跟蹤時(shí),默認(rèn)情況下Cookie保存在瀏覽器進(jìn)程使用的內(nèi)存中,并沒(méi)有保存到磁盤(pán)上,因此瀏覽器關(guān)閉后,Cookie內(nèi)容消失,相應(yīng)的SessionID也就不再存在,所以再次打開(kāi)瀏覽器發(fā)送請(qǐng)求時(shí),已無(wú)法發(fā)送上次的SessionID,也就無(wú)法在服務(wù)器端關(guān)聯(lián)上次的HttpSession對(duì)象了。

2.4 URL重寫(xiě)

有時(shí),用戶由于某些原因禁止了瀏覽器的Cookie功能,Servlet規(guī)范中還引入了一種補(bǔ)充的會(huì)話管理機(jī)制,它允許不支持Cookie的瀏覽器也可以與Web服務(wù)器保持連續(xù)的會(huì)話。這種補(bǔ)充機(jī)制要求在需要加入同一會(huì)話的每個(gè)URL后附加一個(gè)特殊參數(shù),其值為會(huì)話標(biāo)識(shí)號(hào)(SessionID)。當(dāng)用戶單擊響應(yīng)消息中的超鏈接發(fā)出下一次請(qǐng)求時(shí),如果請(qǐng)求消息中沒(méi)有包含Cookie頭字段,Servlet容器則認(rèn)為瀏覽器不支持Cookie,它將根據(jù)請(qǐng)求URL參數(shù)中的SessionID來(lái)實(shí)施會(huì)話跟蹤。將SessionID以參數(shù)形式附加在URL地址后的技術(shù)稱為URL重寫(xiě)。

HttpServletResponse接口中定義了兩個(gè)用于完成URL重寫(xiě)的方法。

encodeURL():用于對(duì)超鏈接或Form表單的action屬性中設(shè)置的URL進(jìn)行重寫(xiě)。

encodeRedirectURL():用于對(duì)要傳遞給HttpServletResponse.sendRedirect()方法的URL進(jìn)行重寫(xiě)。

注意 encodeURL()和encodeRedirectURL()方法根據(jù)請(qǐng)求消息中是否包含Cookie頭字段來(lái)決定是否進(jìn)行URL重寫(xiě)。

下述步驟用于實(shí)現(xiàn)任務(wù)描述2.D.3,基于任務(wù)描述2.D.2,通過(guò)URL重寫(xiě)來(lái)實(shí)現(xiàn)Session會(huì)話技術(shù)。

(1)修改描述2.D.2中FirstServlet.java代碼片段,使用encodeURL()方法對(duì)下述代碼進(jìn)行重寫(xiě)。

    out.println("<a href='s2'>下一頁(yè)</a>");

改為:

    out.println("<a href=" + response.encodeURL("s2") + ">下一頁(yè)</a>");

(2)禁用IE的Cookie功能,重新啟動(dòng)IE,訪問(wèn)http://localhost:8080/ch02/session.html,查看網(wǎng)頁(yè)源文件,可以觀察到超鏈接內(nèi)容如下:

    <a href=s2;jsessionid=EE7406414267CAC832A2A08B2FCC1A7E>下一頁(yè)</a>

通過(guò)單擊URL重寫(xiě)后的超鏈接,服務(wù)器能夠識(shí)別同一瀏覽器發(fā)出的請(qǐng)求,從而實(shí)現(xiàn)了會(huì)話功能。

注意 由于Tomcat發(fā)送給瀏覽器的SessionID的Cookie名稱為jsessionid,因此,Tomcat服務(wù)器中的URL重寫(xiě)就是在URL中附加了jsessionid參數(shù),其值為SessionID的值。

2.5 ServletContext接口

Servlet上下文是運(yùn)行Servlet的邏輯容器。同一個(gè)上下文中的所有Servlet共享存于其中的信息和屬性。在Servlet API中定義了一個(gè)ServletContext接口,用于存取Servlet運(yùn)行的環(huán)境或者上下文信息。ServletContext對(duì)象可以通過(guò)使用ServletConfig對(duì)象的getServletContext()方法獲得,在Servlet中提供了getServletContext()方法也可以直接獲得ServletContext對(duì)象。

2.5.1 ServletContext的方法

ServletContext接口中定義了許多有用的方法,如表2-3所示。

表2-3 ServletContext方法列表

其中g(shù)etAttribute()、setAttribute()、removeAttribute()和getInitParameter()是在Web開(kāi)發(fā)中比較常用的方法,具體的使用方法會(huì)在本章后續(xù)內(nèi)容的示例中講解。

2.5.2 ServletContext的生命周期

ServletContext的生命周期過(guò)程如下:

01 新Servlet容器啟動(dòng)的時(shí)候,服務(wù)器端會(huì)創(chuàng)建一個(gè)ServletContext對(duì)象。

02 在容器運(yùn)行期間ServletContext對(duì)象一直存在。

03 當(dāng)容器停止時(shí),ServletContext的生命周期結(jié)束。

2.5.3 ServletContext示例

下述步驟用于實(shí)現(xiàn)任務(wù)描述2.D.4,使用Servlet上下文保存訪問(wèn)人數(shù)。(1)IndexServlet是所有客戶端訪問(wèn)網(wǎng)站時(shí)首先需要訪問(wèn)的Servlet。每當(dāng)有一個(gè)客戶訪問(wèn)該Servlet時(shí),人數(shù)將加1,并且保存到Servlet上下文中,這樣,在此應(yīng)用中的任何程序都可以訪問(wèn)到該計(jì)數(shù)器的值。

【描述2.D.3】IndexServlet.java

    public class IndexServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            ServletContext  ctx  =  this.getServletContext();
            synchronized (this) {
                Integer  counter  =  (Integer)  ctx.getAttribute("UserNumber");
                int tmp = 0;
                // 如果counter為null,
                // 說(shuō)明servlet上下文中還沒(méi)有設(shè)置UserNumber屬性
                // 此次訪問(wèn)為第一次訪問(wèn)
                if (counter == null) {
                    counter = new Integer(1);
                } else {
                    // 取出原來(lái)計(jì)數(shù)器的值加上1
                    tmp = counter.intValue() + 1;
                    counter = new Integer(tmp);
                }
                ctx.setAttribute("UserNumber",  counter);
            }
            response.setContentType("text/html;charset=GBK");
            PrintWriter out = response.getWriter();
            out.println("<HTML>");
            out.println("<HEAD><TITLE>首頁(yè)</TITLE></HEAD>");
            out.println("<BODY>");
            out.println("這是第一頁(yè)<BR>");
            out.println("<a href='UserNumber'>人數(shù)統(tǒng)計(jì)</a>");
            out.println("</BODY></HTML>");
        }
    }

在上述代碼中,首先通過(guò)HttpServlet的getServletContext()方法獲得對(duì)應(yīng)的ServletContext對(duì)象。然后,通過(guò)ServletContext的getAttribute()方法讀取名為“UserNumber”的屬性值,如果這個(gè)屬性值不存在(返回值null),說(shuō)明“UserNumber”還沒(méi)有被設(shè)置,此次訪問(wèn)為第一次訪問(wèn),否則,將其中的計(jì)數(shù)值讀取出來(lái)加上1,再寫(xiě)回到上下文中。另外,為了防止多個(gè)客戶同時(shí)訪問(wèn)這個(gè)Servlet引起數(shù)據(jù)不同步問(wèn)題,此處使用synchronized進(jìn)行了同步控制。

(2)在另一個(gè)Servlet程序UserNumber.java中讀取保存在Servlet上下文中的數(shù)據(jù)。

【描述2.D.3】UserNumber.java

    public class UserNumber extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            ServletContext  ctx  =  this.getServletContext();
            Integer  counter  =  (Integer)  ctx.getAttribute("UserNumber");

            response.setContentType("text/html;charset=GBK");
            PrintWriter out = response.getWriter();
            out.println("<HTML>");
            out.println("<HEAD><TITLE>訪問(wèn)人數(shù)統(tǒng)計(jì)</TITLE></HEAD>");
            out.println("<BODY>");

            if (counter != null) {
                out.println("已經(jīng)有" + counter.intValue() + "人次訪問(wèn)本網(wǎng)站!");
            } else {
                out.println("你是第一個(gè)訪問(wèn)本網(wǎng)站的!");
            }
            out.println("</BODY></HTML>");
        }
    }

在上述代碼中,也是先通過(guò)HttpServlet的getServletContext()方法獲得對(duì)應(yīng)的Servlet上下文對(duì)象;然后通過(guò)ServletContext對(duì)象的getAttribute()方法來(lái)獲得計(jì)數(shù)器的值,并且將它輸出到客戶端。

啟動(dòng)Tomcat,在IE中訪問(wèn)http://localhost:8080/ch02/IndexServlet,運(yùn)行結(jié)果如圖2-9所示。

單擊“人數(shù)統(tǒng)計(jì)”超鏈接,查看人數(shù),運(yùn)行結(jié)果如圖2-10所示。

再新打開(kāi)兩個(gè)IE進(jìn)程窗口,進(jìn)行訪問(wèn),運(yùn)行結(jié)果如圖2-11所示。

圖2-9 訪問(wèn)IndexServlet

圖2-10 訪問(wèn)統(tǒng)計(jì)

圖2-11 訪問(wèn)統(tǒng)計(jì)

2.5.4 初始化參數(shù)和ServletConfig

ServletContext中除了存取和Web應(yīng)用全局相關(guān)的屬性外,還可以通過(guò)getInitParameter()方法獲得設(shè)置在web.xml中的初始化參數(shù)。

下述代碼用于實(shí)現(xiàn)任務(wù)描述2.D.4,訪問(wèn)web.xml中的初始化參數(shù)信息。

(1)在web.xml中設(shè)置參數(shù)信息。

【描述2.D.4】在web.xml中配置初始化參數(shù)

    <web-app>
    <!-- 初始化參數(shù) -->
        <context-param>
            <!-- 參數(shù)名 -->
            <param-name>serverName</param-name>
            <!-- 參數(shù)值 -->
            <param-value>localhost</param-value>
        </context-param>
        <context-param>
            <param-name>dbInstance</param-name>
            <param-value>nitpro</param-value>
        </context-param>
        <context-param>
            <param-name>userName</param-name>
            <param-value>system</param-value>
        </context-param>
        <context-param>
            <param-name>userPwd</param-name>
            <param-value>manager</param-value>
        </context-param>
    <!--其他配置-->
    </web-app>

在該web.xml中,設(shè)置了4個(gè)全局初始化參數(shù),它們的名字為“serverName”、“dbInstance”、“userName”和“userPwd”。

(2)在Servlet中訪問(wèn)web.xml初始化參數(shù),并輸出。

【描述2.D.4】InitParamServlet.java

    public class InitParamServlet extends HttpServlet {
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html;charset=GBK");
            PrintWriter out = response.getWriter();
            // 獲得ServletContext對(duì)象
            ServletContext  ctx  =  this.getServletContext();
            // 獲得web.xml中設(shè)置的初始化參數(shù)
            String  serverName   =  ctx.getInitParameter("serverName");
            String  dbInstance   =  ctx.getInitParameter("dbInstance");
            String  userName  =  ctx.getInitParameter("userName");
            String  password  =  ctx.getInitParameter("userPwd");
            out.println("<HTML>");
            out.println("<HEAD><TITLE>");
            out.println("讀取初始化參數(shù)</TITLE></HEAD>");
            out.println("<BODY>");
            out.println("服務(wù)器:" + serverName + "<br>");
            out.println("數(shù)據(jù)庫(kù)實(shí)例:" + dbInstance + "<br>");
            out.println("用戶名稱:" + userName + "<br>");
            out.println("用戶密碼:" + password + "<br>");
            out.println("</BODY></HTML>");
        }
    }

在上述代碼中,通過(guò)使用ServletContext對(duì)象的getInitParameter()方法獲得在web.xml中設(shè)置的初始化參數(shù)。

啟動(dòng)Tomcat,在IE中訪問(wèn)http://localhost:8080/ch02/InitParamServlet,運(yùn)行結(jié)果如圖2-12所示。

圖2-12 讀取初始化參數(shù)

初始化參數(shù)也可以通過(guò)使用ServletConfig對(duì)象中的getInitParameter()方法來(lái)獲得,例如:

    ServletConfig sc = getServletConfig();
    name = sc.getInitParameter("userName ");

注意 Servlet容器初始化一個(gè)Servlet對(duì)象時(shí),會(huì)為該Servlet對(duì)象分配一個(gè)ServletConfig對(duì)象。ServletConfig對(duì)象包含Servlet的初始化參數(shù)信息,它與ServletContext關(guān)聯(lián)。ServletConfig和ServletContext主要區(qū)別如下:

(1)ServletConfig作用于某個(gè)特定的Servlet。

(2)ServletContext作用于整個(gè)Web應(yīng)用,是所有Servlet的上下文環(huán)境。

小結(jié)

通過(guò)本章的學(xué)習(xí),學(xué)生應(yīng)該能夠?qū)W會(huì):

■ Cookie是保存在客戶端的小段文本。

■ 通過(guò)請(qǐng)求可以獲得Cookie,通過(guò)響應(yīng)可以寫(xiě)入Cookie。

■ Session是瀏覽器與服務(wù)器之間的一次通話,它包含瀏覽器與服務(wù)器之間的多次請(qǐng)求、響應(yīng)過(guò)程。

■ Session可以在用戶訪問(wèn)一個(gè)Web站點(diǎn)的多個(gè)頁(yè)面時(shí)共享信息。

■ 在Servlet中通過(guò)request.getSession()獲取當(dāng)前Session對(duì)象。

■ 關(guān)閉瀏覽器、調(diào)用Session的invalidate()方法或者等待Session超時(shí)都可以使Session失效。

■ HttpSession使用getAttribute()和setAttribute()方法讀寫(xiě)數(shù)據(jù)。

■ ServletContext是運(yùn)行Servlet的容器。

■ 在Servlet中可通過(guò)getServletContext()方法獲取ServletContext實(shí)例。

■ ServletContext使用getAttribute()和setAttribute()方法讀寫(xiě)數(shù)據(jù)。

練習(xí)

1. 下列關(guān)于Cookie的說(shuō)法正確的是______。(多選)

A. Cookie保存在客戶端

B. Cookie可以被服務(wù)器端程序修改

C. Cookie中可以保存任意長(zhǎng)度的文本

D. 瀏覽器可以關(guān)閉Cookie功能

2. 寫(xiě)入和讀取Cookie的代碼分別是______。

A. request.addCookies()和response.getCookies()

B. response.addCookie()和request.getCookie()

C. response.addCookies()和request.getCookies()

D. response.addCookie()和request.getCookies()

3. Tomcat的默認(rèn)端口號(hào)是______。

A. 80

B. 8080

C. 8088

D. 8000

4. HttpServletRequest的______方法可以得到會(huì)話。(多選)

A. getSession()

B. getSession(boolean)

C. getRequestSession()

D. getHttpSession()

5. 下列選項(xiàng)中可以關(guān)閉會(huì)話的是______。(多選)

A. 調(diào)用HttpSession的close()方法

B. 調(diào)用HttpSession的invalidate()方法

C. 等待HttpSession超時(shí)

D. 調(diào)用HttpServletRequest的getSession(false)方法

6. 在HttpSession中寫(xiě)入和讀取數(shù)據(jù)的方法是______。

A. setParameter()和getParameter()

B. setAttribute()和getAttribute()

C. addAttribute()和getAttribute()

D. set()和get()

7. 下列關(guān)于ServletContext的說(shuō)法正確的是______。(多選)

A. 一個(gè)應(yīng)用對(duì)應(yīng)一個(gè)ServletContext

B. ServletContext的范圍比Session的范圍要大

C. 第一個(gè)會(huì)話在ServletContext中保存了數(shù)據(jù),第二個(gè)會(huì)話讀取不到這些數(shù)據(jù)

D. ServletContext使用setAttribute()和getAttribute()方法操作數(shù)據(jù)

8. 關(guān)于HttpSession的getAttribute()和setAttribute()方法,正確的說(shuō)法是______。(多選)

A. getAttribute()方法返回類型是String

B. getAttribute()方法返回類型是Object

C. setAttribute()方法保存數(shù)據(jù)時(shí)如果名字重復(fù)會(huì)拋出異常

D. setAttribute()方法保存數(shù)據(jù)時(shí)如果名字重復(fù)會(huì)覆蓋以前的數(shù)據(jù)

9. 使HttpSession失效的三種方式是______、______和______。

10. 測(cè)試在其他瀏覽器下Session的生命周期,如Firefox、Chrome等。

主站蜘蛛池模板: 高州市| 和林格尔县| 宜良县| 曲沃县| 大田县| 台湾省| 宁波市| 惠水县| 伊通| 阿荣旗| 沾化县| 蚌埠市| 北流市| 南岸区| 蓬安县| 横山县| 华亭县| 班玛县| 华池县| 霍林郭勒市| 收藏| 白沙| 玛多县| 高青县| 阿克陶县| 类乌齐县| 龙里县| 繁昌县| 班玛县| 舟山市| 天镇县| 江华| 湖北省| 交城县| 阳曲县| 上犹县| 阜新| 都江堰市| 杨浦区| 洛隆县| 曲阳县|