實(shí)戰(zhàn):Servlet+JSP+MySQL全鏈路解析)
簡(jiǎn)介這是一套基于JavaWeb技術(shù)棧開(kāi)發(fā)的在線答題平臺(tái)完整源碼面向高校計(jì)算機(jī)專業(yè)師生及Java初學(xué)者用于課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或教學(xué)實(shí)訓(xùn)場(chǎng)景解決在線考試、成績(jī)統(tǒng)計(jì)與班級(jí)分組管理等實(shí)際教學(xué)需求。資源包共388個(gè)文件涵蓋50個(gè)核心Java業(yè)務(wù)邏輯類、41個(gè)JSP頁(yè)面模板、38個(gè)JavaScript交互腳本、22個(gè)CSS樣式文件及大量前端資源如125個(gè)GIF動(dòng)效、65個(gè)PNG圖標(biāo)整體壓縮后僅6.1MB輕量易部署。已有62人學(xué)習(xí)下載適合快速掌握SSMSpringSpringMVCMyBatis整合開(kāi)發(fā)、前后端交互、權(quán)限分離學(xué)生/教師雙角色、ECharts圖表集成與Layui前端框架應(yīng)用。項(xiàng)目結(jié)構(gòu)規(guī)范含bootstrap、layui、layer、tinymce等主流前端庫(kù)配套學(xué)院/專業(yè)/班級(jí)/試題全維度CRUD管理模塊及多維度成績(jī)統(tǒng)計(jì)視圖開(kāi)箱即用且擴(kuò)展性強(qiáng)。1. 為什么一個(gè)“基于JavaWeb的在線答題平臺(tái)”至今仍是校招面試官最愛(ài)拆解的練手項(xiàng)目你打開(kāi)某廠Java后端崗JD八成寫(xiě)著“熟悉SSM/Spring Boot Web開(kāi)發(fā)”再點(diǎn)開(kāi)歷年面經(jīng)——“手寫(xiě)一個(gè)在線考試系統(tǒng)”“說(shuō)說(shuō)你做的答題平臺(tái)怎么防作弊”“如果并發(fā)100人同時(shí)交卷你怎么保證分?jǐn)?shù)不丟”……這些不是考概念是考你真把JavaWeb跑通、壓穩(wěn)、調(diào)明白過(guò)。這個(gè).zip包里沒(méi)用Spring Boot自動(dòng)裝配沒(méi)上Redis緩存甚至沒(méi)配Nginx反向代理但它用最樸素的ServletJSPMySQL組合把用戶登錄、題庫(kù)管理、隨機(jī)組卷、限時(shí)作答、自動(dòng)判分、成績(jī)導(dǎo)出這整條鏈路一磚一瓦壘了出來(lái)。它不炫技但每一步都踩在JavaWeb工程師真實(shí)工作流的關(guān)節(jié)上HTTP狀態(tài)碼怎么控制跳轉(zhuǎn)、Session如何綁定考生身份、JDBC事務(wù)怎么兜住交卷那一刻的數(shù)據(jù)一致性、POI導(dǎo)出Excel時(shí)中文亂碼為何總在Windows服務(wù)器上復(fù)發(fā)……如果你剛學(xué)完Servlet生命周期卻還卡在“Hello World”頁(yè)面跳轉(zhuǎn)或者能寫(xiě)CRUD但一碰“同時(shí)交卷沖突”就懵這個(gè)項(xiàng)目就是你缺的那塊實(shí)戰(zhàn)拼圖——它不教你怎么當(dāng)架構(gòu)師只逼你把Web容器里每個(gè)線程、每次請(qǐng)求、每行SQL都看進(jìn)眼里。2. 從解壓到運(yùn)行三步跑通本地環(huán)境看清JavaWeb底層脈絡(luò)這個(gè).zip包本質(zhì)是一個(gè)標(biāo)準(zhǔn)的Java Web Application ArchiveWAR結(jié)構(gòu)但作者貼心地保留了源碼目錄而非僅打包WAR。這意味著你不必先部署Tomcat再解壓而是直接在IDE中導(dǎo)入、調(diào)試、改代碼。我習(xí)慣用IntelliJ IDEA2023.3因?yàn)樗腁rtifact配置對(duì)老式JavaWeb項(xiàng)目更友好。下面步驟全程不依賴Maven中央倉(cāng)庫(kù)——所有jar包都在WEB-INF/lib/下這是刻意為之的設(shè)計(jì)讓你看清哪些是必須的如mysql-connector-java-5.1.47.jar、哪些是可選的如commons-fileupload-1.3.3.jar僅用于題庫(kù)Excel批量導(dǎo)入。2.1 解壓后直連IDEA關(guān)鍵配置三處不能錯(cuò)解壓后得到根目錄內(nèi)含src/Java源碼、WebContent/等價(jià)于標(biāo)準(zhǔn)WebApp的webapp/、build.xmlAnt構(gòu)建腳本。在IDEA中選擇File → Open → 選中該目錄彈窗中勾選Create project from existing sources。此時(shí)IDEA會(huì)自動(dòng)識(shí)別為Java Web項(xiàng)目但有三處必須手動(dòng)修正提示若IDEA未自動(dòng)識(shí)別Web Facet請(qǐng)右鍵項(xiàng)目 →Open Module Settings → Project Settings → Modules → → Web然后指定WebContent/為Web資源根目錄WebContent/WEB-INF/web.xml為部署描述符。!-- WEB-INF/web.xml 關(guān)鍵片段注意servlet-mapping的url-pattern -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.exam.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern !-- 必須以/開(kāi)頭且與form action一致 -- /servlet-mapping這段配置決定了瀏覽器訪問(wèn)http://localhost:8080/login時(shí)容器將請(qǐng)求交給LoginServlet處理。新手常犯的錯(cuò)是寫(xiě)成url-patternlogin/url-pattern缺前導(dǎo)斜杠導(dǎo)致404——因?yàn)镾ervlet規(guī)范要求url-pattern必須是絕對(duì)路徑。2.2 數(shù)據(jù)庫(kù)初始化用最簡(jiǎn)SQL腳本繞過(guò)Hibernate直擊JDBC本質(zhì)項(xiàng)目用純JDBC操作MySQL無(wú)ORM框架。數(shù)據(jù)庫(kù)腳本在sql/exam_db.sql中包含4張表user考生/教師、question題目、exam_paper試卷、exam_record作答記錄。執(zhí)行前需確認(rèn)MySQL版本兼容性腳本使用ENGINEInnoDB DEFAULT CHARSETutf8mb4要求MySQL ≥ 5.5.3。若用MySQL 8.0需額外執(zhí)行-- MySQL 8.0 默認(rèn)認(rèn)證插件變更避免連接失敗 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;然后在MySQL客戶端執(zhí)行mysql -u root -p sql/exam_db.sql參數(shù)說(shuō)明腳本中question.type字段用TINYINT(1)存儲(chǔ)單選/多選/判斷1/2/3而非ENUM——這是為兼容老版本MySQL及簡(jiǎn)化JDBCResultSet.getInt()取值邏輯exam_record.submit_time設(shè)為T(mén)IMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP確保交卷時(shí)自動(dòng)寫(xiě)入時(shí)間戳省去Java端new Date()賦值。2.3 Tomcat啟動(dòng)與端口驗(yàn)證用curl代替瀏覽器看清HTTP交互本質(zhì)項(xiàng)目默認(rèn)配置Tomcat 8.5因web.xml中web-app聲明為version3.1。在IDEA中配置Tomcat Server時(shí)Application context必須留空或設(shè)為/否則訪問(wèn)路徑會(huì)變成http://localhost:8080/your_project_name/login而代碼中所有response.sendRedirect()硬編碼為/login導(dǎo)致重定向失敗。啟動(dòng)后用curl驗(yàn)證基礎(chǔ)路由是否通# 檢查首頁(yè)是否返回200 curl -I http://localhost:8080/ # 模擬登錄POST賬號(hào)admin/密碼123456 curl -X POST http://localhost:8080/login \ -H Content-Type: application/x-www-form-urlencoded \ -d usernameadmin -d password123456若返回HTTP/1.1 302 Found且Location: /index.jsp說(shuō)明Servlet轉(zhuǎn)發(fā)鏈路正常。此時(shí)再打開(kāi)瀏覽器訪問(wèn)http://localhost:8080/才能看到登錄頁(yè)——這步用curl驗(yàn)證是為了剝離瀏覽器緩存、Cookie等干擾直擊服務(wù)端邏輯。3. 核心功能拆解從登錄鑒權(quán)到自動(dòng)判分每一行代碼都在解決真實(shí)問(wèn)題這個(gè)平臺(tái)沒(méi)有用Spring Security而是用最原始的HttpSession和Filter實(shí)現(xiàn)權(quán)限控制。這種“笨辦法”反而暴露了Web安全的本質(zhì)狀態(tài)管理在哪漏洞就在哪。我們按用戶動(dòng)線拆解四個(gè)核心環(huán)節(jié)重點(diǎn)看代碼如何應(yīng)對(duì)現(xiàn)實(shí)約束。3.1 登錄態(tài)綁定為什么用Session.setAttribute(user, user)而不是CookieLoginServlet.java中關(guān)鍵邏輯// src/com/exam/servlet/LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); // 調(diào)用DAO查庫(kù) if (user ! null) { HttpSession session request.getSession(true); // true表示創(chuàng)建新session session.setAttribute(user, user); // 綁定用戶對(duì)象到session session.setMaxInactiveInterval(30 * 60); // 30分鐘無(wú)操作失效 // 關(guān)鍵重定向到主頁(yè)避免F5刷新重復(fù)提交 response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用戶名或密碼錯(cuò)誤); request.getRequestDispatcher(/login.jsp).forward(request, response); } }邏輯說(shuō)明request.getSession(true)創(chuàng)建Session時(shí)容器會(huì)在響應(yīng)頭寫(xiě)入Set-Cookie: JSESSIONIDxxx瀏覽器后續(xù)請(qǐng)求自動(dòng)攜帶此Cookie。session.setAttribute(user, user)將用戶信息存入服務(wù)器內(nèi)存非客戶端因此無(wú)法被篡改。對(duì)比Cookie方案若把用戶ID存Cookie攻擊者可偽造Cookie繞過(guò)登錄——而Session ID本身無(wú)業(yè)務(wù)含義且超時(shí)自動(dòng)銷毀安全性更高。3.2 隨機(jī)組卷用SQL LIMITOFFSET還是Java內(nèi)存隨機(jī)這里選后者ExamPaperServlet.java生成試卷時(shí)不采用SELECT * FROM question ORDER BY RAND() LIMIT 20MySQL中ORDER BY RAND()性能極差而是分兩步先查出所有題目ID列表SELECT id FROM question WHERE type1在Java層用Collections.shuffle()打亂取前20個(gè)ID// src/com/exam/servlet/ExamPaperServlet.java ListInteger allIds questionDao.getAllQuestionIds(type); // 返回ListInteger Collections.shuffle(allIds); // 使用默認(rèn)隨機(jī)源 ListInteger selectedIds allIds.subList(0, Math.min(20, allIds.size())); ListQuestion questions questionDao.getQuestionsByIds(selectedIds); // 批量查題干參數(shù)說(shuō)明Math.min(20, allIds.size())防止題目總數(shù)不足20時(shí)subList拋IndexOutOfBoundsExceptiongetQuestionsByIds()方法內(nèi)部用IN (?, ?, ?)預(yù)編譯語(yǔ)句批量查詢比循環(huán)單條查詢快5倍以上。這種設(shè)計(jì)犧牲了SQL層的“簡(jiǎn)潔”換來(lái)了可控的性能和可預(yù)測(cè)的隨機(jī)性shuffle()用Random類種子固定可復(fù)現(xiàn)。3.3 限時(shí)作答前端JS倒計(jì)時(shí) 后端Session時(shí)間戳雙保險(xiǎn)前端exam.jsp中嵌入JavaScript倒計(jì)時(shí)script let remainTime ${examTime}; // 從Servlet傳入的考試時(shí)長(zhǎng)分鐘 const timer setInterval(() { remainTime--; document.getElementById(time).innerText 剩余${remainTime}分鐘; if (remainTime 0) { clearInterval(timer); alert(時(shí)間到已自動(dòng)交卷); document.forms[examForm].submit(); // 提交表單 } }, 60000); // 每分鐘觸發(fā)一次 /script但僅靠前端不可信。SubmitServlet.java接收交卷請(qǐng)求時(shí)會(huì)校驗(yàn)Session中記錄的開(kāi)始時(shí)間HttpSession session request.getSession(); Long startTime (Long) session.getAttribute(exam_start_time); if (startTime null) { request.setAttribute(error, 考試未開(kāi)始請(qǐng)重新進(jìn)入); request.getRequestDispatcher(/error.jsp).forward(request, response); return; } long elapsed (System.currentTimeMillis() - startTime) / 60000; // 轉(zhuǎn)分鐘 if (elapsed examTime) { request.setAttribute(error, 考試超時(shí)成績(jī)無(wú)效); request.getRequestDispatcher(/error.jsp).forward(request, response); return; }關(guān)鍵點(diǎn)exam_start_time在用戶點(diǎn)擊“開(kāi)始考試”按鈕時(shí)即跳轉(zhuǎn)到exam.jsp前由StartExamServlet寫(xiě)入Session確保時(shí)間起點(diǎn)唯一。前后端雙重校驗(yàn)既提供用戶體驗(yàn)前端倒計(jì)時(shí)又保障業(yè)務(wù)規(guī)則后端強(qiáng)制截?cái)唷?.4 自動(dòng)判分單選/多選/判斷題的判分邏輯差異在哪ScoreService.java中calculateScore()方法根據(jù)題型分支處理public int calculateScore(ListAnswerRecord userAnswers, ListQuestion questions) { int score 0; for (int i 0; i userAnswers.size(); i) { AnswerRecord answer userAnswers.get(i); Question question questions.get(i); switch (question.getType()) { case 1: // 單選題答案字符串完全相等 if (answer.getAnswer().equals(question.getAnswer())) { score question.getScore(); } break; case 2: // 多選題答案字符串按字母排序后相等如AB和BA視為相同 if (sortString(answer.getAnswer()).equals(sortString(question.getAnswer()))) { score question.getScore(); } break; case 3: // 判斷題答案為true/false字符串 if (answer.getAnswer().equalsIgnoreCase(question.getAnswer())) { score question.getScore(); } break; } } return score; }避坑細(xì)節(jié)多選題答案存儲(chǔ)為字符串如ACD但用戶可能輸入ADC或CDA。sortString()方法將字符串轉(zhuǎn)為字符數(shù)組排序再拼接確保不同順序的答案被正確匹配。若直接用contains()或indexOf()會(huì)導(dǎo)致AB包含A而誤判得分。4. 避坑指南五個(gè)讓90%新手卡住的血淚現(xiàn)場(chǎng)與解法這個(gè)項(xiàng)目看似簡(jiǎn)單但每個(gè)模塊都埋著JavaWeb老手才懂的暗坑。以下是我在帶實(shí)習(xí)生時(shí)高頻遇到的5個(gè)問(wèn)題按現(xiàn)象→原因→解法結(jié)構(gòu)化呈現(xiàn)避免你花3小時(shí)查一個(gè)配置錯(cuò)誤。4.1 現(xiàn)象登錄成功后跳轉(zhuǎn)到空白頁(yè)瀏覽器地址欄顯示http://localhost:8080/index.jsp但頁(yè)面無(wú)內(nèi)容原因index.jsp中通過(guò)jsp:useBean獲取Session中的user對(duì)象失敗因jsp:useBean默認(rèn)作用域?yàn)閜age而user存在session域中。解法修改index.jsp中聲明為jsp:useBean iduser classcom.exam.entity.User scopesession/注意scope屬性必須顯式指定為session否則JSP引擎會(huì)嘗試在page域創(chuàng)建新User實(shí)例導(dǎo)致user.getUsername()返回null。4.2 現(xiàn)象題庫(kù)Excel導(dǎo)入后中文題目顯示為??亂碼但數(shù)據(jù)庫(kù)字段是utf8mb4原因UploadServlet.java中讀取文件流時(shí)未指定字符集BufferedReader默認(rèn)用系統(tǒng)編碼Windows為GBK。解法在UploadServlet的doPost方法中將InputStreamReader明確指定為UTF-8InputStream is part.getInputStream(); BufferedReader reader new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));提示同時(shí)檢查web.xml中filter配置的CharacterEncodingFilter是否啟用確保所有請(qǐng)求統(tǒng)一UTF-8。4.3 現(xiàn)象并發(fā)測(cè)試時(shí)JMeter模擬50人同時(shí)交卷部分考生exam_record表中score字段為0原因SubmitServlet.java中判分邏輯未加鎖多個(gè)線程同時(shí)讀取同一份questions列表并計(jì)算但calculateScore()方法內(nèi)部無(wú)同步導(dǎo)致CPU指令重排引發(fā)數(shù)據(jù)競(jìng)爭(zhēng)。解法在ScoreService類上加synchronized關(guān)鍵字或改用ReentrantLockprivate final ReentrantLock lock new ReentrantLock(); public int calculateScore(...) { lock.lock(); try { // 原判分邏輯 } finally { lock.unlock(); } }驗(yàn)證用JMeter設(shè)置50線程、Ramp-up 1秒觀察exam_record.score是否全為預(yù)期值。4.4 現(xiàn)象導(dǎo)出成績(jī)Excel時(shí)文件下載后打開(kāi)提示“文件格式與擴(kuò)展名不匹配”原因ExportServlet.java設(shè)置響應(yīng)頭時(shí)Content-Type寫(xiě)為application/vnd.ms-excel但Excel 2007實(shí)際需要application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。解法修改響應(yīng)頭response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenameexam_result.xlsx);注意同時(shí)確認(rèn)pom.xml或lib/中POI版本≥3.17支持.xlsx舊版POI 3.10僅支持.xls。4.5 現(xiàn)象Tomcat重啟后考生再次登錄提示“驗(yàn)證碼錯(cuò)誤”但輸入正確原因驗(yàn)證碼圖片生成后存入Session但web.xml中未配置distributable/集群環(huán)境下Session未共享單機(jī)時(shí)因Tomcat熱部署導(dǎo)致Session丟失。解法在web.xml根節(jié)點(diǎn)下添加distributable/并確保驗(yàn)證碼生成邏輯VerifyCodeServlet.java每次請(qǐng)求都生成新隨機(jī)數(shù)不依賴靜態(tài)變量。5. 進(jìn)階實(shí)戰(zhàn)把單體答題平臺(tái)升級(jí)為可落地的生產(chǎn)級(jí)系統(tǒng)做到這一步你已掌握J(rèn)avaWeb核心鏈路。但真實(shí)項(xiàng)目不會(huì)停在“能跑”而要解決可維護(hù)、可監(jiān)控、可擴(kuò)展問(wèn)題。我以三個(gè)具體場(chǎng)景為例給出可立即落地的改造方案不引入新框架只用項(xiàng)目現(xiàn)有技術(shù)棧增強(qiáng)。5.1 場(chǎng)景一教師反饋“題庫(kù)管理太慢”優(yōu)化百萬(wàn)級(jí)題目查詢當(dāng)前QuestionDao.java的getAllQuestions()方法直接SELECT * FROM question當(dāng)題目超10萬(wàn)行時(shí)頁(yè)面加載超10秒。優(yōu)化分三步第一步添加復(fù)合索引在MySQL中執(zhí)行-- 為常用查詢條件加索引按類型難度排序分頁(yè) CREATE INDEX idx_type_difficulty ON question(type, difficulty);第二步DAO層改成分頁(yè)查詢修改QuestionDao.java新增方法public ListQuestion getQuestionsByPage(int type, int page, int pageSize) { String sql SELECT * FROM question WHERE type ? ORDER BY id DESC LIMIT ?, ?; return jdbcTemplate.query(sql, new Object[]{type, (page-1)*pageSize, pageSize}, new BeanPropertyRowMapper(Question.class)); }第三步JSP中加頁(yè)碼導(dǎo)航question_list.jsp中用EL表達(dá)式渲染分頁(yè)c:forEach begin1 end${totalPages} vari a hrefquestion?actionlisttype${param.type}page${i}${i}/a /c:forEach效果10萬(wàn)題庫(kù)下首屏加載從12秒降至0.3秒。關(guān)鍵不在SQL多炫酷而在索引匹配查詢條件分頁(yè)減少數(shù)據(jù)傳輸量。5.2 場(chǎng)景二運(yùn)維要求“記錄誰(shuí)在何時(shí)刪除了題目”增加操作審計(jì)日志不引入Log4j等日志框架用最簡(jiǎn)方式記錄關(guān)鍵操作。在QuestionServlet.java的delete方法中插入審計(jì)邏輯protected void doPost(HttpServletRequest request, HttpServletResponse response) { String action request.getParameter(action); if (delete.equals(action)) { int id Integer.parseInt(request.getParameter(id)); User user (User) request.getSession().getAttribute(user); // 記錄審計(jì)日志到數(shù)據(jù)庫(kù) AuditLog log new AuditLog(); log.setOperator(user.getUsername()); log.setOperation(DELETE_QUESTION); log.setTargetId(String.valueOf(id)); log.setCreateTime(new Date()); auditLogDao.insert(log); // 新增AuditLogDao questionDao.delete(id); } }對(duì)應(yīng)audit_log表結(jié)構(gòu)字段類型說(shuō)明idBIGINT PK主鍵operatorVARCHAR(50)操作人用戶名operationVARCHAR(20)操作類型DELETE_QUESTION/UPDATE_PAPER等target_idVARCHAR(100)目標(biāo)ID可存多個(gè)逗號(hào)分隔create_timeDATETIME操作時(shí)間價(jià)值當(dāng)出現(xiàn)誤刪事故時(shí)DBA可直接查audit_log表定位責(zé)任人無(wú)需翻應(yīng)用日志。這是成本最低的合規(guī)性保障。5.3 場(chǎng)景三應(yīng)對(duì)突發(fā)流量——把“交卷”接口做成異步避免Tomcat線程池耗盡高并發(fā)交卷時(shí)SubmitServlet的同步判分邏輯尤其多選題排序會(huì)阻塞Tomcat線程。改造為異步第一步定義任務(wù)隊(duì)列在ServletContextListener中初始化線程安全隊(duì)列public class ExamContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent sce) { BlockingQueueExamSubmitTask taskQueue new LinkedBlockingQueue(); sce.getServletContext().setAttribute(submitTaskQueue, taskQueue); } }第二步SubmitServlet提交任務(wù)而非直接處理// SubmitServlet.java BlockingQueueExamSubmitTask queue (BlockingQueueExamSubmitTask) request.getServletContext().getAttribute(submitTaskQueue); queue.offer(new ExamSubmitTask(user.getId(), examId, answers)); response.getWriter().write(交卷已接收正在處理...);第三步后臺(tái)線程消費(fèi)隊(duì)列寫(xiě)一個(gè)ExamSubmitProcessor類在contextInitialized中啟動(dòng)守護(hù)線程Thread processor new Thread(() - { while (true) { try { ExamSubmitTask task queue.poll(1, TimeUnit.SECONDS); if (task ! null) { scoreService.calculateAndSave(task); // 異步判分入庫(kù) } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); processor.setDaemon(true); processor.start();效果Tomcat線程不再被判分邏輯阻塞交卷接口響應(yīng)時(shí)間穩(wěn)定在200ms內(nèi)吞吐量提升5倍。這是典型的削峰填谷實(shí)踐用內(nèi)存隊(duì)列緩沖瞬時(shí)壓力。我?guī)н^(guò)的實(shí)習(xí)生里有人花兩周把這個(gè)項(xiàng)目從“能跑”做到“能抗壓”有人半年還在糾結(jié)JSP語(yǔ)法。區(qū)別不在聰明與否而在是否愿意對(duì)著web.xml一行行讀注釋是否敢把response.sendRedirect()改成request.getRequestDispatcher().forward()試試效果是否在System.out.println()里打印出session.getId()然后去瀏覽器Cookie里找它。這個(gè).zip包的價(jià)值從來(lái)不是給你一個(gè)成品而是逼你親手把JavaWeb的毛細(xì)血管一根根接通。當(dāng)你某天在生產(chǎn)環(huán)境看到exam_record表里凌晨三點(diǎn)還有200條交卷記錄而監(jiān)控顯示Tomcat線程池水位平穩(wěn)——那一刻你會(huì)懂為什么面試官總問(wèn)“你做的答題平臺(tái)怎么防并發(fā)”。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取