Ajax (Asynchronous JavaScript and XML) 讓瀏覽器能在不重新整理頁面的情況下,與伺服器非同步交換資料,構建媲美桌面應用的豐富 Web 體驗。本文以「動態購物車」為例,完整展示從前端 `XMLHttpRequest` 封裝、後端 Servlet 處理、XML 回應序列化、DOM 更新的全鏈路實作。
XMLHttpRequest`,註冊回調,發送 POST 請求至 `cart.do`。function newXMLHttpRequest() {
var xmlreq = false;
if (window.XMLHttpRequest) { // 標準瀏覽器 (IE7+, Firefox, Chrome, Safari)
xmlreq = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE6 及以下
try { xmlreq = new ActiveXObject("Msxml2.XMLHTTP"); }
catch (e1) {
try { xmlreq = new ActiveXObject("Microsoft.XMLHTTP"); }
catch (e2) { /* 不支援 */ }
}
}
return xmlreq;
}
function addToCart(itemCode) {
var req = newXMLHttpRequest();
var handler = getReadyStateHandler(req, updateCart); // 回調包裝
req.onreadystatechange = handler;
req.open("POST", "cart.do", true); // 非同步
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("action=add&item=" + encodeURIComponent(itemCode));
}
// 通用 readyState 處理器:過濾 readyState===4 並檢查 HTTP 200
function getReadyStateHandler(req, callback) {
return function() {
if (req.readyState == 4) {
if (req.status == 200) callback(req.responseXML);
else alert("請求失敗: " + req.status);
}
};
}
function updateCart(cartXML) {
var items = cartXML.getElementsByTagName("item");
var cartList = document.getElementById("cart-contents");
var totalEl = document.getElementById("total");
cartList.innerHTML = ""; // 清空現有列表
var total = 0.0;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var name = item.getElementsByTagName("name")[0].firstChild.nodeValue;
var price = parseFloat(item.getElementsByTagName("price")[0].firstChild.nodeValue);
var qty = parseInt(item.getElementsByTagName("quantity")[0].firstChild.nodeValue);
var li = document.createElement("li");
li.appendChild(document.createTextNode(name + " x " + qty + " = $" + (price * qty).toFixed(2)));
cartList.appendChild(li);
total += price * qty;
}
totalEl.firstChild.nodeValue = "$" + total.toFixed(2);
}
@WebServlet("/cart.do")
public class CartServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String action = req.getParameter("action");
String itemCode = req.getParameter("item");
HttpSession session = req.getSession(true);
Cart cart = (Cart) session.getAttribute("cart");
if (cart == null) { cart = new Cart(); session.setAttribute("cart", cart); }
if ("add".equals(action)) {
cart.addItem(itemCode); // 業務邏輯封裝在 Cart JavaBean
}
// 序列化為 XML
resp.setContentType("application/xml;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("");
out.println("");
for (CartItem ci : cart.getItems()) {
out.println(" - ");
out.println("
" + escapeXml(ci.getName()) + " ");
out.println(" " + ci.getPrice() + " ");
out.println(" " + ci.getQuantity() + " ");
out.println(" ");
}
out.println(" ");
}
private String escapeXml(String s) {
return s.replace("&", "&").replace("<", "<").replace(">", ">")
.replace("\"", """).replace("'", "'");
}
}
public class Cart implements Serializable {
private Map items = new LinkedHashMap<>(); // 保持插入順序
public void addItem(String itemCode) {
CartItem ci = items.get(itemCode);
if (ci == null) {
// 實際應從資料庫/快取查詢商品詳情
ci = new CartItem(itemCode, "商品" + itemCode, 19.99);
items.put(itemCode, ci);
}
ci.incrementQuantity();
}
public List getItems() { return new ArrayList<>(items.values()); }
}
public class CartItem implements Serializable {
private String code, name; private double price; private int quantity;
// 建構子、getter、setter、incrementQuantity()...
}
<!-- 商品列表 -->
<button onclick="addToCart('hat001')">加入購物車</button>
<!-- 購物車顯示區 (非同步更新) -->
<ul id="cart-contents"></ul>
總價: <span id="total">$0.00</span>
| 決策點 | 建議 | 理由 |
|---|---|---|
| 資料格式 | XML (教學) / JSON (實務) | JSON 解析更快、體積更小、JS 原生支援 |
| 狀態儲存 | Session 中放 JavaBean | 物件導向、型別安全、易擴充 |
| 同步機制 | Session 屬性同步 (叢集需 Sticky Session 或 Session 複製) | 簡單有效,大型系統改用 Redis 分散式 Session |
| 錯誤處理 | HTTP 狀態碼 + XML/JSON 錯誤碼 | 前端統一攔截顯示友善訊息 |
| 安全性 | CSRF Token、XSS 防護 (escapeXml)、HTTPS | 防範跨站偽造請求與腳本注入 |
action="cart.do" method="POST"` 提交,頁面重新整理顯示結果。aria-live="polite"` 標示動態更新區域,螢幕閱讀器可感知變化。Ajax 讓 Java Web 應用脫離「請求-回應-整頁重繪」的傳統模式,邁向單頁應用 (SPA) 時代的先鋒技術。掌握其核心機制,是通往現代前後端分離架構的必經之路。