傳統表單提交 = 整頁重載 + 用戶等待。Ajax (Asynchronous JavaScript and XML) 透過瀏覽器內建 XMLHttpRequest (XHR) 實現**局部異步數據交換**,用戶無感知等待,體驗質變。本文實現註冊頁三大核心異步交互:
javax.imageio 繪圖web.xml Servlet 映射UserServlet)
package com.webapp.servlet;
import com.webapp.util.CheckCodeImageUtil;
import javax.imageio.ImageIO;
import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
public class UserServlet extends HttpServlet {
private List userList = new ArrayList<>(); // 模擬用戶庫
private String rulesFilePath;
@Override
public void init() throws ServletException {
rulesFilePath = getInitParameter("rulesfilepath"); // /txt/item.txt
// 預置用戶
userList.addAll(Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String path = req.getRequestURI().substring(req.getContextPath().length());
// 路徑格式:/validatename.do, /rulesText.do, /checkcode.do
String action = path.substring(1, path.indexOf('.'));
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
switch (action) {
case "validatename":
handleValidateName(req, resp);
break;
case "rulesText":
handleRulesText(req, resp);
break;
case "checkcode":
handleCheckCode(req, resp);
break;
default:
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
private void handleValidateName(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 模擬網路延遲 3 秒,體現異步優勢
try { Thread.sleep(3000); } catch (InterruptedException ignored) {}
String username = req.getParameter("username");
resp.setContentType("text/plain;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.print(userList.contains(username) ? "exists" : "noexists");
}
private void handleRulesText(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
PrintWriter out = resp.getWriter();
String realPath = getServletContext().getRealPath(rulesFilePath);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(realPath), "GBK"))) { // 檔案編碼 GBK
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
out.print(sb.toString());
} catch (Exception e) {
out.print("服務條款載入失敗,請稍後再試。");
}
}
private void handleCheckCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("image/jpeg");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
Map map = CheckCodeImageUtil.creatCheckImage();
String code = map.keySet().iterator().next();
BufferedImage image = map.get(code);
req.getSession().setAttribute("validateCode", code); // 存入 Session 驗證
ImageIO.write(image, "jpeg", resp.getOutputStream());
}
}
CheckCodeImageUtil)
package com.webapp.util;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.*;
public class CheckCodeImageUtil {
private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray(); // 排除易混淆字元
private static final Random RANDOM = new Random();
public static Map creatCheckImage() {
int width = 100, height = 38, codeCount = 4;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 背景
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// 邊框
g.setColor(Color.GRAY);
g.drawRect(0, 0, width - 1, height - 1);
// 干擾線 (30 條)
g.setColor(Color.LIGHT_GRAY);
for (int i = 0; i < 30; i++) {
int x1 = RANDOM.nextInt(width);
int y1 = RANDOM.nextInt(height);
int x2 = RANDOM.nextInt(20);
int y2 = RANDOM.nextInt(20);
g.drawLine(x1, y1, x1 + x2, y1 + y2);
}
// 字體
Font font = new Font("Consolas", Font.BOLD, 28);
g.setFont(font);
// 生成驗證碼
StringBuilder code = new StringBuilder();
for (int i = 0; i < codeCount; i++) {
char c = CHARS[RANDOM.nextInt(CHARS.length)];
code.append(c);
// 隨機顏色
g.setColor(new Color(RANDOM.nextInt(100), RANDOM.nextInt(100), RANDOM.nextInt(100)));
g.drawString(String.valueOf(c), 20 * i + 10, 30);
}
g.dispose();
Map map = new HashMap<>();
map.put(code.toString(), image);
return map;
}
}
regist.jsp)
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
用戶註冊 - Ajax 異步演示
regist_ajax.js)
// 跨瀏覽器創建 XHR
function createXHR() {
if (typeof XMLHttpRequest !== 'undefined') return new XMLHttpRequest();
try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {}
throw new Error('不支援 XMLHttpRequest');
}
var xhr = createXHR(); // 單例複用 (簡單頁面足夠,複雜場景請每請求創建)
// 防緩存時間戳
function addTimestamp(url) {
return url + (url.indexOf('?') === -1 ? '?' : '&') + '_t=' + Date.now();
}
// 通用 GET 請求
function get(url, callback) {
xhr.open('GET', addTimestamp(encodeURI(url)), true);
xhr.onreadystatechange = function() { handleResponse(callback); };
xhr.send(null);
}
// 通用 POST 請求
function post(url, data, callback) {
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() { handleResponse(callback); };
xhr.send(data);
}
function handleResponse(callback) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callback(xhr.responseText);
} else {
callback('error');
}
}
}
// 1. 用戶名校驗 (離焦觸發 POST)
function validateUsername() {
var username = document.getElementById('username').value.trim();
if (!username) { showMsg('checkMsg', '請輸入用戶名', 'orange'); return; }
showMsg('checkMsg', '檢查中...', '#0099aa');
post('validatename.do', 'username=' + encodeURIComponent(username), function(result) {
if (result === 'exists') showMsg('checkMsg', '* 對不起,該用戶名已存在', 'red');
else if (result === 'noexists') showMsg('checkMsg', '* 恭喜,該用戶名可用', 'green');
else showMsg('checkMsg', '* 系統故障,請稍後再試', 'red');
});
}
// 2. 載入服務條款 (頁面載入時 GET)
function loadRulesText() {
var ta = document.getElementById('rulesText');
get('rulesText.do', function(text) {
ta.value = text || '條款載入失敗';
});
}
// 3. 載入/刷新驗證碼 (頁面載入 + 點擊圖片)
function loadCheckCode() {
var img = document.getElementById('checkCodeImg');
img.src = addTimestamp('checkcode.do'); // 修改 src 觸發新請求
}
// 顯示提示訊息
function showMsg(id, text, color) {
var el = document.getElementById(id);
el.innerHTML = '' + text + '';
}
// 表單提交前綜合驗證
function validateForm(form) {
var user = form.username.value.trim();
var pwd = form.password.value;
var repwd = form.repassword.value;
var email = form.email.value.trim();
var code = form.checkcode.value.trim();
if (!user) return alertMsg('checkMsg', '請輸入用戶名');
if (!/^\w+$/.test(user)) return alertMsg('checkMsg', '用戶名只能含字母數字下劃線');
if (pwd.length < 6 || pwd.length > 12) return alertMsg('pwdMsg', '密碼長度 6-12 位');
if (pwd !== repwd) return alertMsg('repwdMsg', '兩次密碼不一致');
if (!/^[\w.-]+@[\w.-]+\.\w+$/.test(email)) return alertMsg('emailMsg', '郵箱格式不正確');
if (code.length !== 4) return alertMsg('ckcodeMsg', '請輸入 4 位驗證碼');
// 可在此增加驗證碼與 Session 比對的 Ajax 同步驗證
return true; // 通過提交
}
function alertMsg(id, msg) {
showMsg(id, '* ' + msg, 'red');
document.getElementById(id.replace('Msg', '')).focus();
return false;
}
regist.css)
.reg-table {
margin: 50px auto; width: 720px; border-collapse: collapse;
font: 14px/1.6 "Microsoft YaHei", sans-serif; border: 1px solid #ddd;
}
.reg-table td { padding: 12px 10px; }
.label { width: 100px; text-align: right; color: #555; background: #f5faff; font-weight: 500; }
.input { border: 1px solid #b0d0f0; width: 280px; height: 32px; padding: 0 8px; border-radius: 3px; }
.input:focus { border-color: #4a90d9; outline: none; box-shadow: 0 0 4px rgba(74,144,217,0.3); }
.title { background: #e8f4fd; color: #2a6ebd; font-size: 16px; font-weight: bold; }
.btn-submit { background: #2a6ebd; color: #fff; border: none; padding: 10px 30px; font-size: 16px; border-radius: 4px; cursor: pointer; }
.btn-submit:hover { background: #1d5a9e; }
.copyright { text-align: center; margin-top: 20px; color: #888; font-size: 12px; }
#checkCodeImg { vertical-align: middle; cursor: pointer; border: 1px solid #ddd; margin-left: 5px; }
UserServlet
com.webapp.servlet.UserServlet
rulesfilepath
/txt/item.txt
UserServlet
*.do
regist.jsp
_t=timestamp);響應頭設定 Cache-Control: no-cache。fetch API / axios + async/await,代碼更線性易維護。@RestController + Jackson JSON 序列化,統一返回 Result<T> 結構。本專案展示了 Ajax 在傳統 Java Web 中的經典應用模式:**原生 XHR 封裝、Servlet 多動作分發、Session 驗證碼、檔案異步載入、防緩存與編碼處理**。雖然現代開發多採用前後端分離 + JSON API,但理解這套「從零構建異步交互」的完整鏈路,對排查前端請求異常、優化用戶體驗、設計通用組件庫具有根本價值。願你的每一次提交,都能享受「無感刷新」的絲滑體驗!