Java EE之會話

1.須要會話的緣由

全部HTTP服務器技術都廣泛採用HTTP會話的概念,而且Java EE也在規範中添加了對會話的支持。html

維持狀態
會話用於維持請求和請求之間的狀態。HTTP請求自身是徹底無狀態的。從服務器的角度來講,當用戶的Web瀏覽器打開第一個鏈接到服務器的套接字時請求就開始了,直到服務器返回最後一個數據包並關閉鏈接時,該請求將結束。
在無狀態方式下,應用程序一般沒法正常工做。一個經典的例子就是在線購物網站。當你瀏覽商店時,找到了喜歡的商品,就會將它添加到購物車中。而後繼續瀏覽商店並找到另外一個喜歡的商品,一樣也將它添加到購物車中。在查看購物車時,你應該看到其中有兩個商品。在你發出的多個請求之間,網站經過某種方式瞭解到它們是來自於同一計算機中的同一瀏覽器,並將它們關聯到你的購物車。其餘人是沒法看到你的購物車或購物車中包含的產品——購物車只綁定到你的計算機和瀏覽器。java

記住用戶
另外一個須要考慮的場景是用戶論壇網站。用戶在登陸以後,就能夠添加論壇主題、回覆主題、參與其餘用戶的私人討論、向版主舉報主題或回覆、還能夠收藏主題。注意在整個過程當中用戶只須要登陸一次。系統須要經過某種方式記住該用戶,會話就提供了這種功能。瀏覽器

2.使用會話cookie和URL重寫

在Web會話的理論中,會話是由服務器或Web應用程序管理的某些文件、內存片斷、對象或者容器,它包含了分配給它的各類不一樣數據。
一般會話被賦予一個隨機生成的字符串,稱爲會話ID。第一次建立會話時(即收到請求時),建立的會話ID將會做爲響應的一部分返回到用戶瀏覽器中。接下來從該用戶瀏覽器中發出的請求都將經過某種方式包含該會話ID。當應用程序收到含有會話ID的請求時,它能夠經過該ID將現有會話與當前請求關聯起來。安全

注意:使用隨機字符串做爲會話ID而不使用簡單的序列ID,是由於序列ID是可預測的,這樣可能會容易引發會話劫持。服務器

2.1 會話cookie

HTTP 1.1的解決方案HTTP cookie,可用於將會話ID發送到瀏覽器,從而使瀏覽器能夠在將來的請求中包含該會話ID。
cookie有各類不一樣的特性,例如域名、路徑、過時日期或最大生命週期、安全標誌或只含HTTP的標誌。cookie

  • Domian特性告訴瀏覽器應該將cookie發送到哪一個域名中。
  • Path將cookie限制在相對於域名的某個特定URL中
  • Expires定義了cookie的絕對過時日期。
  • 若是存在Secure特性,瀏覽器將只會經過HTTPS發送cookie(這將保護cookie,避免以未加密的方式進行傳輸)。

在Java EE應用服務器中,會話cookie的名字默認爲JSESSIONIDsession

2.2 URL中的會話ID

另外一種傳輸會話ID的流行方式是經過URL。
不一樣的技術對如何在URL中內嵌和定位會話ID使用不一樣的策略。
Java EE應用程序將會話ID添加到URL的最後一個路徑段的矩陣參數中。經過這種方式分離開會話ID與查詢字符串的參數,使它們不會相互衝突。
http://www.example.com/support?JSESSIONID=NRxclGg2vG7kI4MdlLn?foo=bar&high=five
這種方式使瀏覽器意識不到會話ID的存在。相反,服務器將重寫Location頭中的URL以及任何響應內容中URL,使瀏覽器用於訪問服務器的全部URL都已經內嵌了會話ID。
HttpServletResponse接口定義了兩個能夠重寫URL的方法:encodeURLencodeRedirectURL,它們將在必要的時候把會話ID內嵌在URL中。dom

3.在會話中存儲數據

3.1 在部署描述符中配置會話

在許多狀況下,均可以在Java EE中直接使用HTTP會話,不須要添加顯式的配置。不過能夠在部署描述符中配置它們,而且出於安全的目的也應該配置。jsp

<session-config>
        <session-timeout>30</session-timeout>
        <cookie-config>
            <name>JSESSIONID</name>
            <domain>example.org</domain>
            <path>/shop</path>
            <comment><![CDATA[Keeps you logged in. See our privacy policy for more information.]]></comment>
            <http-only>true</http-only>
            <secure>false</secure>
            <max-age>1800</max-age>
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
        <tracking-mode>URL</tracking-mode>
        <tracking-mode>SSL</tracking-mode>
    </session-config>

標籤<session-timeout>以分鐘爲單位,若是它的值小於等於0,那麼會話將永遠也不過時。若是忽略該標籤,那麼它將使用容器的默認值。Tomcat容器的默認值是30分鐘,能夠經過Tomcat配置修改。
Servlet 3.0/Java EE6中新增的標籤<tracking-mode>用於表示容器應該使用哪一種技術追蹤會話ID。它的合法值有:URL、COOKIE和SSL。
只有在追蹤模式中使用了COOKIE時,纔可使用<cookie-config>標籤。
<cookie-config>標籤的標籤:ide

  • 經過標籤<name>能夠自定義會話cookie的名字。默認值爲JSESSIONID。
  • 標籤<domain><path>對應着cookie的Domain和Path特性。Web容器已經設置了正確的默認值,所以一般不須要自定義它們。
  • 標籤<comment>將在會話ID cookie中添加Comment特性,用於解釋cookie的目的。
  • 標籤<http-only><secure>對應着cookie的HttpOnly特性和Secure特性,它們的默認值都是假。
  • 標籤<max-age>指定了cookie的Max-Age特性,用於控制cookie什麼時候過時。默認狀況下,cookie沒有過時日期,這意味着它將在瀏覽器關閉時過時,將它設置爲-1的效果相同。

3.2 存儲和獲取數據

3.2.1 在Servlet中使用會話

private void addToCart(HttpServletRequest request,
                           HttpServletResponse response)
            throws ServletException, IOException
    {
        int productId;
        try
        {
            productId = Integer.parseInt(request.getParameter("productId"));
        }
        catch(Exception e)
        {
            response.sendRedirect("shop");
            return;
        }

        HttpSession session = request.getSession();
        if(session.getAttribute("cart") == null)
            session.setAttribute("cart", new Hashtable<Integer, Integer>());

        @SuppressWarnings("unchecked")
        Map<Integer, Integer> cart =
                (Map<Integer, Integer>)session.getAttribute("cart");
        if(!cart.containsKey(productId))
            cart.put(productId, 0);
        cart.put(productId, cart.get(productId) + 1);

        response.sendRedirect("shop?action=viewCart");
    }

HttpServletRequest的getSession方法有兩種方式:getSession()getSession(boolean)
getSession()的調用實際將會調用getSession(true),若是會話存在,就返回已有會話,不存在,就建立一個新的會話。
若是調用getSession(false),那麼若是會話存在就返回已有會話,不然返回null
HttpSession的getAttribute方法將返回會話中存儲的對象,setAttribute方法將把對象綁定到會話中。

3.2.2 在JSP中使用會話

viewCart.jsp

<%@ page import="java.util.Map" %>
<!DOCTYPE html>
<html>
    <head>
        <title>View Cart</title>
    </head>
    <body>
        <h2>View Cart</h2>
        <a href="<c:url value="/shop" />">Product List</a><br /><br />
        <a href="<c:url value="/shop?action=emptyCart" />">Empty Cart</a><br /><br />
        <%
            @SuppressWarnings("unchecked")
            Map<Integer, String> products =
                    (Map<Integer, String>)request.getAttribute("products");
            @SuppressWarnings("unchecked")
            Map<Integer, Integer> cart =
                    (Map<Integer, Integer>)session.getAttribute("cart");

            if(cart == null || cart.size() == 0)
                out.println("Your cart is empty.");
            else
            {
                for(int id : cart.keySet())
                {
                    out.println(products.get(id) + " (qty: " + cart.get(id) +
                            ")<br />");
                }
            }
        %>
    </body>
</html>

該JSP使用隱式的session變量訪問會話中存儲的商店購物車Map,而後列出了購物車中全部的產品和它們的數量。

示例源碼(在會話中存儲和獲取數據)連接:https://pan.baidu.com/s/1KYDagePVcW0SXzk9WK3Z1Q 密碼:fz21

3.3 刪除數據

private void emptyCart(HttpServletRequest request,
                           HttpServletResponse response)
            throws ServletException, IOException
    {
        request.getSession().removeAttribute("cart");
        response.sendRedirect("shop?action=viewCart");
    }

也能夠經過調用getAttribute方法獲取Map,而後調用Map的clear方法清空數據。這裏只是爲了演示removeAttribute方法的使用。

3.4 在會話中存儲更復雜的數據

理論上講,會話能夠存儲但願存儲的數據。
固然,也必須考慮但願存儲數據的大小。若是在會話中添加了過多數據,那麼有可能會致使虛擬機的內存耗盡。

示例源碼(在會話中存儲更復雜的數據)連接:https://pan.baidu.com/s/1cLQS-qLiYd9LSOiksMCDFA 密碼:ed9f

4.使用會話

Java EE中會話最有用的特性之一就是會話事件。
使用監聽器檢測會話的變化。Servlet API中定義了幾種監聽器,大多數儘管不是所有,都將監聽某種形式的會話活動。經過實現對應事件的監聽器接口訂閱某個事件,而後在部署描述符中添加<listener>配置,或者在該類中添加註解@javax.servlet.annotation.WebListener
當某個事件發生時,將觸發事件的發佈,而後容器將調用對應事件監聽器中的方法。

@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionIdListener
{
    private SimpleDateFormat formatter =
            new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss");

    @Override
    public void sessionCreated(HttpSessionEvent e)
    {
        System.out.println(this.date() + ": Session " + e.getSession().getId() +
                " created.");
        SessionRegistry.addSession(e.getSession());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent e)
    {
        System.out.println(this.date() + ": Session " + e.getSession().getId() +
                " destroyed.");
        SessionRegistry.removeSession(e.getSession());
    }

    @Override
    public void sessionIdChanged(HttpSessionEvent e, String oldSessionId)
    {
        System.out.println(this.date() + ": Session ID " + oldSessionId +
                " changed to " + e.getSession().getId());
        SessionRegistry.updateSessionId(e.getSession(), oldSessionId);
    }

    private String date()
    {
        return this.formatter.format(new Date());
    }
}

或者在部署描述符中配置

<listener>
        <listener-class>com.wrox.SessionListener</listener-class>
    </listener>

示例源碼(使用會話)連接:https://pan.baidu.com/s/1r4Ojmj22MuXRA3EzIITdTA 密碼:5ejb

相關文章
相關標籤/搜索