首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java Web 會話管理精髓:Cookie 與 Session 深度解析、實戰案例與作用域對比

     編輯:Java 綜合教學

核心概念辨析

特性CookieSession
存儲位置客戶端 (瀏覽器記憶體/硬碟)伺服器端 (記憶體/資料庫/Redis)
容量限制單個 4KB、域名下約 20-50 個理論無上限,受伺服器記憶體限制
安全性可被竄改/攔截/盜用 (需 HttpOnly/Secure/SameSite)數據不出伺服器,僅傳遞 JSESSIONID,相對安全
生命週期會話級 (關閉瀏覽器銷毀) / 持久級 (設定 Max-Age)預設 30 分鐘無活動超時,可配置
跨域支援受同源策略限制,可設 Domain/Path 跨子域需共享存儲 (Redis/Database) 實現集群 Session
依賴關係獨立依賴 Cookie 傳遞 JSESSIONID (或 URL 重寫)

Cookie API 完全指南

寫入 Cookie (響應端)

Cookie cookie = new Cookie("username", "zhangsan"); // 名值對,不支援中文需 URLEncoder
cookie.setMaxAge(60 * 60 * 24 * 7); // 7 天持久化,秒為單位;-1=會話級;0=刪除
cookie.setPath("/"); // 生效路徑,建議根路徑全站可訪問
cookie.setHttpOnly(true); // 防 XSS,JS 無法讀取
cookie.setSecure(true); // 僅 HTTPS 傳輸 (生產必開)
response.addCookie(cookie);

讀取 Cookie (請求端)

Cookie[] cookies = request.getCookies();
if (cookies != null) {
    for (Cookie c : cookies) {
        if ("username".equals(c.getName())) {
            String value = URLDecoder.decode(c.getValue(), StandardCharsets.UTF_8);
            // 使用 value
        }
    }
}

// 工具類封裝
public static Cookie getCookie(String name, Cookie[] cookies) {
    if (cookies == null) return null;
    for (Cookie c : cookies) if (name.equals(c.getName())) return c;
    return null;
}

兩類 Cookie 對比

  • 會話 Cookie:setMaxAge(-1) (預設),存瀏覽器記憶體,關閉瀏覽器即銷毀。
  • 持久 Cookie:setMaxAge(>0),寫入硬碟,跨瀏覽器會話保留,過期自動刪除。

實戰案例一:記錄上次訪問時間

@WebServlet("/visit")
public class VisitServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=UTF-8");
        Cookie c = CookieUtil.getCookie("lastVisit", req.getCookies());
        if (c == null) {
            resp.getWriter().println("歡迎首次訪問!");
        } else {
            long time = Long.parseLong(c.getValue());
            resp.getWriter().println("上次訪問: " + new Date(time));
        }
        // 更新為當前時間
        Cookie newCookie = new Cookie("lastVisit", String.valueOf(System.currentTimeMillis()));
        newCookie.setMaxAge(60 * 60 * 24 * 30); // 30 天
        newCookie.setPath("/");
        resp.addCookie(newCookie);
    }
}

實戰案例二:商品瀏覽歷史 (去重、最多 5 條)

@WebServlet("/product/detail")
public class ProductDetailServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String id = req.getParameter("id"); // 商品 ID
        Cookie c = CookieUtil.getCookie("history", req.getCookies());
        String history = (c != null) ? c.getValue() : "";
        String[] ids = history.split(",");
        
        // 去重並前置
        List<String> list = new ArrayList<>(Arrays.asList(ids));
        list.remove(id);
        list.add(0, id);
        if (list.size() > 5) list = list.subList(0, 5);
        
        Cookie newCookie = new Cookie("history", String.join(",", list));
        newCookie.setMaxAge(60 * 60 * 24 * 30);
        newCookie.setPath("/");
        resp.addCookie(newCookie);
        
        // 轉發到詳情頁
        req.getRequestDispatcher("/detail.jsp").forward(req, resp);
    }
}

Session API 完全指南

獲取/創建 Session

HttpSession session = request.getSession(); // 若無則創建,等同 getSession(true)
HttpSession session = request.getSession(false); // 僅獲取,無則返回 null

JSP 預隱含 session 物件 (除非 <%@ page session="false" %>)。

屬性操作

session.setAttribute("user", userObj); // 存入
User user = (User) session.getAttribute("user"); // 取出
session.removeAttribute("user"); // 刪除單屬性
session.invalidate(); // 銷毀整個 Session

生命週期控制

  • web.xml 全域配置:<session-config><session-timeout>30</session-timeout></session-config> (分鐘)。
  • 程式控制:session.setMaxInactiveInterval(1800) (秒)。
  • 銷毀觸發:超時、invalidate()、伺服器非正常關閉 (正常關閉會序列化到 work 目錄)。

關閉瀏覽器後保持 Session

預設 JSESSIONID 為會話 Cookie,關閉瀏覽器即失效。解決:手動寫入持久 Cookie:

HttpSession session = request.getSession();
Cookie jsession = new Cookie("JSESSIONID", session.getId());
jsession.setMaxAge(60 * 60 * 24); // 1 天
jsession.setPath("/");
response.addCookie(jsession);

前提:Session 物件本身未超時銷毀。

實戰案例三:一次性驗證碼 (登入/註冊)

// 生成端 (CheckCodeServlet)
@WebServlet("/checkCode")
public class CheckCodeServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String code = generateRandomCode(4); // 如 "A3K9"
        req.getSession().setAttribute("checkCode", code);
        // 返回圖片流...
    }
}

// 驗證端 (RegisterServlet)
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String input = req.getParameter("checkCode");
        String saved = (String) req.getSession().getAttribute("checkCode");
        req.getSession().removeAttribute("checkCode"); // 一次性,用完即銷毀
        if (saved != null && saved.equalsIgnoreCase(input)) {
            // 註冊成功邏輯
            resp.sendRedirect("/login.jsp");
        } else {
            req.setAttribute("error", "驗證碼錯誤");
            req.getRequestDispatcher("/register.jsp").forward(req, resp);
        }
    }
}

實戰案例四:購物車 (Session 存 Map<商品, 數量>)

@WebServlet("/cart/add")
public class CartAddServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String id = req.getParameter("id");
        Product p = productService.getById(id);
        
        Map<Product, Integer> cart = (Map<Product, Integer>) req.getSession().getAttribute("cart");
        if (cart == null) cart = new LinkedHashMap<>(); // 保持插入順序
        
        cart.merge(p, 1, Integer::sum); // 存在則 +1
        req.getSession().setAttribute("cart", cart);
        resp.getWriter().println("加入成功 <a href='/cart/view'>查看購物車</a>");
    }
}

@WebServlet("/cart/view")
public class CartViewServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Map<Product, Integer> cart = (Map<Product, Integer>) req.getSession().getAttribute("cart");
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        if (cart == null || cart.isEmpty()) {
            out.println("購物車為空");
        } else {
            out.println("<h3>購物車</h3><ul>");
            cart.forEach((p, qty) -> out.println("<li>" + p.getName() + " x " + qty + "</li>"));
            out.println("</ul>");
        }
    }
}

三大作用域對比與選型指南

作用域生命週期可見範圍典型用途建議使用頻率
ServletContext (application)伺服器啟動~關閉全 Web 應用 (所有用戶、所有請求)全域配置、緩存、連接池、訪問計數低 (謹慎使用,佔用堆內存)
HttpSession (session)30 分鐘無活動 / invalidate單用戶多請求登入用戶、購物車、權限、表單向導中 (核心業務狀態)
HttpServletRequest (request)單次請求 (轉發鏈共享)單次請求處理鏈Controller → View 傳遞數據、表單回顯、臨時計算結果高 (首選,生命週期最短、無併發風險)

同步方法對比

  • setAttribute(name, value) / getAttribute(name) / removeAttribute(name) — 三大作用域通用。
  • 請求屬性隨轉發傳遞,Session/Application 跨請求共享。

總結

  • Cookie 適合輕量、非敏感、客戶端持久化需求 (記住我、瀏覽歷史)。
  • Session 適合敏感、大體積、伺服器端狀態 (用戶身份、購物車、權限)。
  • Session 底層依賴 Cookie 傳遞 JSESSIONID,理解這一點能解決 90% 的 Session 失效疑難雜症。
  • 請求屬性最安全、效能最好,能用 Request 就不用 Session,能用 Session 就不用 Application。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved