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

Java Web Session 購物車實作進階:完整架構與最佳實務

     編輯:Java 相關知識

前言

本文作為購物車實作的進階補充,著重於架構分層、例外處理、併發安全與擴展性設計,幫助開發者從「能跑通」邁向「可維護、可上線」。

分層架構重構

cn.com.shopping
├── entity
│   └── Book.java              # 實體類別
├── dao
│   └── BookDao.java           # 資料存取介面
│   └── BookDaoImpl.java       # 實作類(可切換 DB/記憶體)
├── service
│   └── CartService.java       # 業務邏輯介面
│   └── CartServiceImpl.java   # 購物車核心邏輯
├── web
│   ├── ListBookServlet.java
│   ├── AddToCartServlet.java
│   ├── ViewCartServlet.java
│   └── RemoveFromCartServlet.java
└── util
    └── SessionUtil.java       # Session 操作工具類

核心服務層實作

CartService 介面

package cn.com.shopping.service;

import cn.com.shopping.entity.Book;
import java.util.List;

public interface CartService {
    void addItem(HttpSession session, Book book);
    List getCartItems(HttpSession session);
    void removeItem(HttpSession session, String bookId);
    void clearCart(HttpSession session);
    int getItemCount(HttpSession session);
}

CartServiceImpl 實作

package cn.com.shopping.service;

import cn.com.shopping.dao.BookDao;
import cn.com.shopping.entity.Book;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;

public class CartServiceImpl implements CartService {
    private static final String CART_KEY = "shopping_cart";
    private BookDao bookDao = new BookDaoImpl();

    @Override
    public void addItem(HttpSession session, Book book) {
        @SuppressWarnings("unchecked")
        List cart = (List) session.getAttribute(CART_KEY);
        if (cart == null) {
            cart = new ArrayList<>();
            session.setAttribute(CART_KEY, cart);
        }
        // 併發安全:同步區塊鎖定 Session
        synchronized (session) {
            cart.add(book);
        }
    }

    @Override
    public List getCartItems(HttpSession session) {
        @SuppressWarnings("unchecked")
        List cart = (List) session.getAttribute(CART_KEY);
        return cart != null ? new ArrayList<>(cart) : new ArrayList<>(); // 回傳防禦性副本
    }

    @Override
    public void removeItem(HttpSession session, String bookId) {
        @SuppressWarnings("unchecked")
        List cart = (List) session.getAttribute(CART_KEY);
        if (cart != null) {
            synchronized (session) {
                cart.removeIf(book -> book.getId().equals(bookId));
            }
        }
    }

    @Override
    public void clearCart(HttpSession session) {
        session.removeAttribute(CART_KEY);
    }

    @Override
    public int getItemCount(HttpSession session) {
        @SuppressWarnings("unchecked")
        List cart = (List) session.getAttribute(CART_KEY);
        return cart != null ? cart.size() : 0;
    }
}

Servlet 薄控制層

package cn.com.shopping.web;

import cn.com.shopping.entity.Book;
import cn.com.shopping.service.CartService;
import cn.com.shopping.service.CartServiceImpl;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/cart/add")
public class AddToCartServlet extends HttpServlet {
    private CartService cartService = new CartServiceImpl();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String bookId = req.getParameter("bookId");
        if (bookId == null || bookId.isEmpty()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "缺少商品 ID");
            return;
        }

        Book book = new BookDaoImpl().findById(bookId);
        if (book == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "商品不存在");
            return;
        }

        HttpSession session = req.getSession(true);
        cartService.addItem(session, book);

        // AJAX 請求回傳 JSON,傳統請求重導向
        String accept = req.getHeader("Accept");
        if (accept != null && accept.contains("application/json")) {
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"success\":true,\"count\":" + cartService.getItemCount(session) + "}");
        } else {
            resp.sendRedirect(req.getContextPath() + "/cart/view");
        }
    }
}

關鍵最佳實務總結

1. 併發安全

Session 物件非執行緒安全,多個 AJAX 請求並發操作購物車時,必須加鎖:

synchronized (session) {
    cart.add(book);
}

2. 防禦性複製

服務層回傳購物車列表時,回傳副本而非原始引用,防止控制層意外修改:

return cart != null ? new ArrayList<>(cart) : new ArrayList<>();

3. 統一 Session Key 管理

定義常數避免硬編碼字串散落:

private static final String CART_KEY = "shopping_cart";

4. 異常處理與 HTTP 語意

  • 缺少參數 → 400 Bad Request
  • 商品不存在 → 404 Not Found
  • 系統錯誤 → 500 Internal Server Error + 記錄日誌

5. 支援 AJAX 與傳統雙模式

透過 Accept 標頭判斷回傳格式,前後端分離與傳統 MVC 兼容。

部署與測試建議

  • 使用 @WebServlet 註解替代 web.xml,簡化配置
  • 整合 Log4j2/SLF4J 記錄關鍵操作日誌
  • 撰寫 JUnit + Mockito 單元測試覆蓋 CartService 邏輯
  • 壓力測試驗證 Session 併發性能瓶頸
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved