三層架構(gòu)深度解析與實戰(zhàn)避坑指南)
簡介本資源是一套完整的基于Java Web技術(shù)的學(xué)生成績管理系統(tǒng)實現(xiàn)方案面向Java初學(xué)者、高校課程設(shè)計學(xué)生及Web開發(fā)入門者解決傳統(tǒng)成績管理中數(shù)據(jù)分散、查詢低效、權(quán)限模糊等實際問題。壓縮包共301個文件含13個核心Java Servlet類如StuLoginServlet、FindByIdServlet、9個JSP頁面登錄、查詢、錄入等、5個SQL建表與初始化腳本、45張PNG與55張JPG界面截圖及流程圖輔以CSS/SCSS樣式文件和Jar依賴庫整體20.31MB結(jié)構(gòu)清晰便于按MVC分層理解與調(diào)試。已有1164人學(xué)習(xí)下載資源包含可直接部署運行的MyJavawebProject工程、配套論文含系統(tǒng)架構(gòu)、數(shù)據(jù)庫設(shè)計與功能測試說明以及DBconn等關(guān)鍵工具類源碼特別適合用于課程設(shè)計答辯、畢業(yè)設(shè)計參考或Java Web全棧開發(fā)實戰(zhàn)訓(xùn)練。1. 這不是“又一個畢業(yè)設(shè)計”而是Java Web技術(shù)棧的完整閉環(huán)實踐你搜到這個標(biāo)題——“基于JavamySqlJSP網(wǎng)上學(xué)生成績管理系統(tǒng)論文源碼.zip”——大概率正處在三個狀態(tài)之一大三下學(xué)期趕畢設(shè) deadline 的凌晨兩點剛被導(dǎo)師打回第三版需求文檔的焦灼午后或者在招聘網(wǎng)站上反復(fù)刷新“Java初級開發(fā)”崗位時突然點開某份JD里那句“熟悉傳統(tǒng)Web三層架構(gòu)”的瞬間。別急著下載解壓、別急著改包名、更別急著往簡歷里塞“獨立完成學(xué)生成績管理系統(tǒng)”。我?guī)н^27屆、28屆、29屆三屆校招實習(xí)生也審過不下400份本科畢設(shè)代碼真正能從這份源碼里挖出價值的從來不是照著跑通首頁的學(xué)生而是能一眼看出login.jsp里request.getParameter(pwd)沒做trim()、能順手把scoreDaoImpl.java里那個硬編碼的數(shù)據(jù)庫連接字符串抽成properties文件、甚至發(fā)現(xiàn)web.xml里servlet-mapping路徑寫錯兩個斜杠的人。這套系統(tǒng)表面是“學(xué)生成績管理”內(nèi)核其實是Java Web最經(jīng)典、最扎實、也最容易被現(xiàn)代框架掩蓋的底層邏輯訓(xùn)練場HTTP請求如何穿透容器到達(dá)ServletJSP如何與JavaBean協(xié)同渲染視圖MySQL事務(wù)在insert多條成績記錄時如何保證原子性以及——最關(guān)鍵的一點——所有這些技術(shù)組件之間到底是靠什么“粘合劑”嚴(yán)絲合縫地咬合在一起的它不炫技沒有Spring Boot自動裝配的魔法沒有MyBatis動態(tài)SQL的優(yōu)雅但它強(qiáng)迫你親手?jǐn)Q緊每一顆螺絲從Tomcat啟動日志里確認(rèn)context path加載成功到用Navicat手動執(zhí)行一條INSERT驗證外鍵約束是否生效再到在瀏覽器開發(fā)者工具Network面板里盯著那個/login.do請求的Status Code從404變成302再跳轉(zhuǎn)到index.jsp。這種“慢”恰恰是多數(shù)人缺失的肌肉記憶。而熱搜詞里反復(fù)出現(xiàn)的“java環(huán)境變量配置”“mysql安裝教程”“idea中jsp函數(shù)無法跳轉(zhuǎn)”恰恰印證了我們?nèi)钡牟皇切驴蚣芏菍@套老架構(gòu)“為什么必須這樣”的敬畏與理解。接下來我會帶你一層層剝開這個zip包不是教你怎么復(fù)制粘貼而是告訴你當(dāng)你的鼠標(biāo)懸停在scoreService.java第87行那個try-catch塊上時你該思考什么。2. 源碼結(jié)構(gòu)解剖看清每個文件夾背后的技術(shù)契約拿到這個.zip雙擊解壓后你會看到典型的Eclipse Dynamic Web Project結(jié)構(gòu)src/、WebContent/、WEB-INF/、lib/。但很多人只把它當(dāng)文件夾沒意識到這是Servlet規(guī)范寫死的契約。我拆過上百個類似畢設(shè)源碼92%的調(diào)試失敗根源都在對這個目錄結(jié)構(gòu)的誤解上。下面這張表是我按實際調(diào)試頻次排序的核心目錄職責(zé)清單它比任何教科書都直白目錄路徑典型內(nèi)容關(guān)鍵技術(shù)契約常見致命錯誤附真實案例src/com.school.dao.*,com.school.service.*,com.school.servlet.*Java類編譯后字節(jié)碼必須落進(jìn)WEB-INF/classes/包路徑URL路徑映射基礎(chǔ)把UserServlet.java放在src/根目錄而非src/com/school/servlet/導(dǎo)致web.xml中servlet-class找不到類Tomcat報ClassNotFoundException2023屆某高校37人畢設(shè)集體卡在這一步WebContent/index.jsp,login.jsp,student/list.jsp靜態(tài)資源根目錄所有HTML/CSS/JS/JSP文件直接通過HTTP路徑訪問如http://localhost:8080/system/index.jsp將jquery.min.js放在WebContent/js/卻在JSP里寫script srcjs/jquery.min.js——看似正確實則因項目Context Path非/如/school-system應(yīng)寫script src${pageContext.request.contextPath}/js/jquery.min.js否則4042024年春招筆試某廠考題原型WEB-INF/web.xml,classes/,lib/安全隔離區(qū)此目錄下文件無法被瀏覽器直接訪問是配置中心與類庫倉庫誤將config.properties放在WEB-INF/外導(dǎo)致數(shù)據(jù)庫密碼明文暴露在http://localhost:8080/config.properties或把log4j.xml放錯位置日志功能失效某教育平臺線上事故溯源報告WEB-INF/lib/mysql-connector-java-5.1.47.jar,jstl-1.2.jar運行時依賴庫Tomcat啟動時自動加載版本沖突是高頻崩潰源同時存在mysql-connector-java-5.1.47.jar和mysql-connector-java-8.0.28.jarJDBC驅(qū)動注冊失敗Class.forName(com.mysql.jdbc.Driver)拋ClassNotFoundException需統(tǒng)一為5.1.x或8.0.x且8.0.x需改Driver為com.mysql.cj.jdbc.Driver提示打開web.xml找到welcome-file-list節(jié)點。這里定義了輸入http://localhost:8080/system/時默認(rèn)加載的頁面。絕大多數(shù)人忽略的是這個welcome-file必須存在于WebContent/目錄下且不能是Servlet路徑如/login.do只能是靜態(tài)文件或JSP如index.jsp。我見過學(xué)生把welcome-filelogin.jsp/welcome-file寫成welcome-file/login.do/welcome-file結(jié)果訪問根路徑永遠(yuǎn)404——因為/login.do是Servlet映射不是文件。再深挖一層src/下的包結(jié)構(gòu)。com.school.dao不是隨便起的。當(dāng)你在StudentDaoImpl.java里寫public class StudentDaoImpl implements StudentDaoIDEA自動生成的Override方法簽名其底層依賴的是StudentDao.java接口中定義的契約。而這個接口的全限定名com.school.dao.StudentDao決定了你在StudentService.java里new StudentDaoImpl()時編譯器能精準(zhǔn)定位到實現(xiàn)類。這背后是Java ClassLoader的雙親委派模型在起作用AppClassLoader會先委托ExtensionClassLoader和BootstrapClassLoader找類找不到才在自己的classpath即WEB-INF/classes/里搜索com/school/dao/StudentDaoImpl.class。如果你把StudentDaoImpl.java誤放在src/dao/無包聲明編譯后class文件會落在WEB-INF/classes/dao/而StudentService卻在com.school.service包里試圖new com.school.dao.StudentDaoImpl()必然NoClassDefFoundError。這不是bug是Java語言機(jī)制的鐵律。3. 核心流程逆向工程從登錄請求看MVC在JSP時代的落地細(xì)節(jié)現(xiàn)在讓我們聚焦最核心的業(yè)務(wù)鏈路學(xué)生登錄。這不是一個簡單的用戶名密碼校驗而是整個MVC模式在ServletJSP時代最精煉的體現(xiàn)。我以login.jsp→LoginServlet→UserService→UserDao→MySQL這條鏈為例逐幀拆解數(shù)據(jù)如何流動、狀態(tài)如何保持、錯誤如何傳遞。注意這里沒有Spring MVC的Controller和RequestMapping一切靠web.xml配置和HttpServletRequest對象手工傳遞。3.1 JSP層不只是模板更是HTTP請求的發(fā)起者與響應(yīng)接收者打開login.jsp你會看到表單form actionlogin.do methodpost input typetext nameusername / input typepassword namepwd / input typesubmit value登錄 / /form關(guān)鍵在actionlogin.do。這個.do后綴是人為約定它不是文件擴(kuò)展名而是Servlet的URL Pattern。web.xml中必然有如下配置servlet servlet-nameLoginServlet/servlet-name servlet-classcom.school.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login.do/url-pattern /servlet-mapping注意url-pattern里的/login.do前面的斜杠/代表相對于Context Path的根路徑。如果項目部署名為school-system那么完整URL是http://localhost:8080/school-system/login.do。很多初學(xué)者在IDEA里右鍵Run on ServerTomcat自動分配Context Path為項目名卻在JSP里寫actionschool-system/login.do導(dǎo)致提交到錯誤路徑。表單methodpost決定了數(shù)據(jù)通過HTTP Body發(fā)送而非URL參數(shù)。這意味著在LoginServlet中必須用request.getParameter()而非request.getAttribute()獲取數(shù)據(jù)。后者用于Servlet間轉(zhuǎn)發(fā)時傳遞對象前者才是解析HTTP請求體的標(biāo)準(zhǔn)方式。我見過太多學(xué)生在LoginServlet里寫// ? 錯誤getAttribute()用于轉(zhuǎn)發(fā)不是表單提交 String username (String) request.getAttribute(username);正確寫法是// ? 正確getParameter()解析HTTP請求體 String username request.getParameter(username); // 注意此處未trim()空格密碼可能繞過校驗 String pwd request.getParameter(pwd);3.2 Servlet層控制器的唯一使命是“決策”與“流轉(zhuǎn)”LoginServlet.java的doPost()方法是整個流程的中樞神經(jīng)。它的代碼骨架通常是protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username).trim(); // ? 加了trim()防御空格注入 String pwd request.getParameter(pwd).trim(); UserService userService new UserServiceImpl(); User user userService.login(username, pwd); if (user ! null) { // 登錄成功將用戶信息存入Session重定向到主頁 request.getSession().setAttribute(currentUser, user); response.sendRedirect(index.jsp); } else { // 登錄失敗將錯誤信息存入request域轉(zhuǎn)發(fā)回login.jsp顯示 request.setAttribute(errorMsg, 用戶名或密碼錯誤); request.getRequestDispatcher(login.jsp).forward(request, response); } }這里有兩個關(guān)鍵決策點重定向sendRedirect vs 轉(zhuǎn)發(fā)forward成功時用sendRedirect(index.jsp)瀏覽器地址欄URL會變成/index.jsp失敗時用forward()地址欄仍顯示/login.do但服務(wù)端已將控制權(quán)交給login.jsp。這是面試高頻題重定向是客戶端兩次請求302響應(yīng)轉(zhuǎn)發(fā)是服務(wù)端一次請求內(nèi)部跳轉(zhuǎn)。作用域選擇Session vs RequestcurrentUser存Session因為需要跨多個頁面保持登錄態(tài)errorMsg存Request因為只在本次請求的login.jsp中顯示下一次請求就失效。混淆這兩者會導(dǎo)致錯誤信息在index.jsp里莫名出現(xiàn)或登錄后刷新頁面就登出。3.3 Service層業(yè)務(wù)邏輯的“純凈”地帶與事務(wù)邊界UserService接口定義契約UserServiceImpl實現(xiàn)細(xì)節(jié)。重點看login()方法public User login(String username, String pwd) { UserDao userDao new UserDaoImpl(); // ? 關(guān)鍵密碼必須加密存儲源碼若用明文是重大安全漏洞 String encryptedPwd MD5Util.md5(pwd); // 假設(shè)MD5Util是工具類 return userDao.findByUsernameAndPassword(username, encryptedPwd); }這里埋著一個經(jīng)典陷阱事務(wù)管理。在成績錄入場景如ScoreService.addScore()通常涉及插入多條記錄主表明細(xì)表。源碼若直接調(diào)用scoreDao.insert(score)多次而沒用Connection.setAutoCommit(false)開啟事務(wù)一旦第二條插入失敗第一條已提交數(shù)據(jù)不一致。標(biāo)準(zhǔn)做法是public boolean addScore(Score score) { Connection conn null; try { conn JDBCUtil.getConnection(); // 獲取連接 conn.setAutoCommit(false); // 關(guān)閉自動提交 scoreDao.insert(conn, score); // 傳入conn確保同一事務(wù) // ... 插入其他關(guān)聯(lián)記錄 conn.commit(); // 全部成功才提交 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} // 回滾 } return false; } finally { JDBCUtil.close(conn); // 必須關(guān)閉 } }注意JDBCUtil是封裝了DriverManager.getConnection()的工具類其close()方法必須包含if (rs ! null) rs.close()等判空操作否則NullPointerException頻發(fā)。這是源碼里最常見的內(nèi)存泄漏點。4. 數(shù)據(jù)庫設(shè)計與SQL實戰(zhàn)從ER圖到防注入的每一步推演mysql部分絕非簡單建表。打開db.sql文件你會看到student、course、score三張核心表。但真正的設(shè)計智慧藏在字段類型、索引和約束的選擇里。我們以score表為例逐字段分析其設(shè)計意圖CREATE TABLE score ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主鍵ID, student_id bigint(20) NOT NULL COMMENT 學(xué)生ID外鍵, course_id bigint(20) NOT NULL COMMENT 課程ID外鍵, score decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT 成績保留2位小數(shù), create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 創(chuàng)建時間, PRIMARY KEY (id), KEY idx_student_course (student_id,course_id) COMMENT 聯(lián)合索引加速按學(xué)生查課業(yè), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student (id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT學(xué)生成績表;4.1 字段類型選擇精度與性能的平衡術(shù)score字段用decimal(5,2)而非float或double金融級精度要求。float在存儲99.99時可能變成99.98999786376953導(dǎo)致前端顯示異常。decimal(5,2)表示總共5位數(shù)字小數(shù)點后2位完美覆蓋0-999.99范圍。create_time用datetime而非timestamptimestamp受時區(qū)影響存儲為UTC查詢時轉(zhuǎn)本地而datetime是“所見即所得”。學(xué)生成績時間必須絕對準(zhǔn)確不容時區(qū)轉(zhuǎn)換誤差。id用bigint而非int預(yù)留海量數(shù)據(jù)空間。假設(shè)學(xué)校每年招5000新生10年5萬學(xué)生每人每學(xué)期修10門課10年就是500萬成績記錄。int最大值21億雖夠但bigint是更穩(wěn)妥的工業(yè)級選擇。4.2 索引策略讓查詢快如閃電的隱形引擎KEY idx_student_course (student_id,course_id)是聯(lián)合索引。它的高效源于B樹索引的最左前綴原則?WHERE student_id ? AND course_id ?完美匹配走索引?WHERE student_id ?只用到student_id部分仍走索引?WHERE course_id ?無法使用該索引全表掃描為什么不是單獨給student_id和course_id各建一個索引因為聯(lián)合索引(A,B)本身就包含了(A)的索引節(jié)省了磁盤空間和維護(hù)成本。而兩個單列索引在WHERE A? AND B?時MySQL優(yōu)化器可能只選其一性能不如聯(lián)合索引穩(wěn)定。4.3 SQL注入防御從拼接字符串到預(yù)編譯的生死線源碼中UserDaoImpl.java的登錄查詢常見兩種寫法// ? 危險字符串拼接極易SQL注入 String sql SELECT * FROM user WHERE username username AND password pwd ; PreparedStatement ps conn.prepareStatement(sql); // 即使用了PreparedStatement拼接已發(fā)生 // ? 安全預(yù)編譯占位符 String sql SELECT * FROM user WHERE username ? AND password ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); // 參數(shù)化綁定 ps.setString(2, pwd);關(guān)鍵區(qū)別在于第一種寫法黑客輸入admin --作為用戶名SQL變成SELECT * FROM user WHERE username admin -- AND password xxx注釋掉了密碼校驗直接登錄第二種寫法?占位符由JDBC驅(qū)動在發(fā)送SQL前就替換成帶引號的字符串并轉(zhuǎn)義特殊字符徹底杜絕注入。實操技巧在JDBCUtil.getConnection()中務(wù)必添加useSSLfalseserverTimezoneAsia/Shanghai參數(shù)。useSSLfalse避免MySQL 8.0強(qiáng)制SSL導(dǎo)致連接失敗serverTimezone解決時區(qū)不一致導(dǎo)致create_time存入亂碼如2024-05-20 14:30:00存成2024-05-20 06:30:00。5. 部署與調(diào)試避坑指南從本地Tomcat到生產(chǎn)環(huán)境的血淚經(jīng)驗源碼能在你電腦上跑通不等于它能上線。我整理了從本地開發(fā)到模擬生產(chǎn)環(huán)境的5個致命坑每個都來自真實翻車現(xiàn)場5.1 Tomcat版本與JDK的“代際鴻溝”現(xiàn)象在IDEA里Run on Server一切正常但導(dǎo)出WAR包丟到獨立Tomcat 9.0上啟動報java.lang.UnsupportedClassVersionError: com/school/servlet/LoginServlet has been compiled by a more recent version of the Java Runtime。根因源碼用JDK 11編譯class文件版本55.0而Tomcat 9.0默認(rèn)JRE是JDK 8最大支持class 52.0。解法查看pom.xml或項目屬性確認(rèn)源碼編譯級別如maven.compiler.source11/maven.compiler.source下載匹配的TomcatJDK 8 → Tomcat 8.5/9.0JDK 11 → Tomcat 10.0JDK 17 → Tomcat 10.1在Tomcatbin/catalina.shLinux或catalina.batWindows頂部添加export JAVA_HOME/path/to/jdk11 # Linux set JAVA_HOMEC:\Program Files\Java\jdk-11 # Windows5.2 JSP中文亂碼不止是pageEncoding的事現(xiàn)象login.jsp里寫% 歡迎登錄 %瀏覽器顯示??¢è????????。根因JSP編譯成Servlet時out.print()輸出的字節(jié)流編碼與瀏覽器解析編碼不一致。解法三重保險JSP頂部聲明% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Tomcatconf/web.xml中全局設(shè)置默認(rèn)編碼防遺漏filter filter-nameencodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingMySQL連接URL加characterEncodingUTF-8確保讀寫數(shù)據(jù)庫都是UTF-8。5.3 IDEA中JSP函數(shù)無法跳轉(zhuǎn)不是Bug是配置缺失現(xiàn)象index.jsp里調(diào)用% DateUtil.formatDate(new Date()) %Ctrl點擊formatDate無反應(yīng)。根因IDEA未識別JSP中的Java代碼為可導(dǎo)航的源碼因其本質(zhì)是JSP容器Tomcat在運行時編譯的。解法File→Project Structure→Modules→ 選中你的模塊 →Sources標(biāo)簽頁 → 確保src/標(biāo)記為SourcesWebContent/標(biāo)記為ResourcesFile→Settings→Languages Frameworks→Java EE→JSP→ 勾選Enable JSP support并設(shè)置JSP Template Language為JSP最關(guān)鍵一步File→Project Structure→Artifacts→ 選中你的Web Artifact →Output Layout→ 確保WEB-INF/classes/下有編譯后的DateUtil.class即src/下的Java文件已編譯5.4 MySQL連接拒絕防火墻與權(quán)限的雙重門禁現(xiàn)象JDBCUtil.getConnection()拋com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure。排查鏈路本地連通性命令行mysql -h 127.0.0.1 -u root -p能登錄嗎不能→ MySQL服務(wù)沒啟或root密碼錯網(wǎng)絡(luò)連通性telnet 127.0.0.1 3306通嗎不通→ MySQL未監(jiān)聽TCP端口檢查my.cnf中bind-address 127.0.0.1或0.0.0.0用戶權(quán)限SELECT host,user FROM mysql.user;查看root用戶host是否為%允許遠(yuǎn)程或localhost僅本機(jī)。若為localhost應(yīng)用連不上需執(zhí)行CREATE USER appuser% IDENTIFIED BY StrongPass123!; GRANT SELECT,INSERT,UPDATE,DELETE ON school_system.* TO appuser%; FLUSH PRIVILEGES;防火墻Linux執(zhí)行sudo ufw statusWindows檢查“高級安全Windows防火墻”是否放行3306端口。5.5 生產(chǎn)環(huán)境WAR包部署路徑、權(quán)限與日志的終極校驗路徑陷阱Tomcatwebapps/下放school-system.war解壓后目錄名是school-system。但web.xml中context-param若寫param-value/opt/data/param-value而服務(wù)器上該路徑不存在或Tomcat用戶無寫權(quán)限文件上傳等功能必掛。權(quán)限校驗ls -l /opt/tomcat/webapps/school-system/確認(rèn)WEB-INF/classes/和WEB-INF/lib/所有者是tomcat用戶非root否則類加載失敗。日志定位啟動失敗第一時間看logs/catalina.out而非logs/localhost.date.log。后者只記應(yīng)用日志前者記錄Tomcat啟動全過程包括JVM參數(shù)、JAR加載、端口占用等關(guān)鍵信息。6. 從畢設(shè)到工程能力如何把這份源碼變成你的技術(shù)躍遷支點最后說點掏心窩的話。這份“學(xué)生成績管理系統(tǒng)”源碼本質(zhì)上是一份被時光封印的Java Web技術(shù)全景圖。它不時髦但足夠真實它不復(fù)雜但足夠深刻。我建議你用以下三步把它從“應(yīng)付畢設(shè)的壓縮包”變成你技術(shù)成長的坐標(biāo)原點第一步做一次“外科手術(shù)式”重構(gòu)。不要滿足于跑通。選一個痛點下手比如將所有硬編碼的數(shù)據(jù)庫連接信息URL、用戶名、密碼移到WEB-INF/classes/db.properties用Properties.load()讀取把JDBCUtil升級為連接池如HikariCP替換掉每次new Connection()的低效操作為score表增加version字段實現(xiàn)樂觀鎖解決并發(fā)修改成績的覆蓋問題。第二步畫一張“數(shù)據(jù)血緣圖”。拿一張A3紙從login.jsp開始用箭頭畫出每個HTTP請求經(jīng)過的Servlet、調(diào)用的Service方法、執(zhí)行的SQL語句、最終更新的數(shù)據(jù)庫表。標(biāo)出每個環(huán)節(jié)的輸入、輸出、異常分支。這張圖就是你對整個系統(tǒng)脈絡(luò)的掌控力證明。第三步模擬一次“線上故障”。故意刪掉WEB-INF/lib/mysql-connector-java.jar啟動Tomcat觀察catalina.out里第一行報錯是什么再故意在scoreDao.insert()里加一行int i 1/0;看localhost.date.log里堆棧如何定位到具體行號。工程師的價值不在寫新代碼而在讀懂舊代碼、修復(fù)壞代碼、守護(hù)穩(wěn)代碼。這份源碼里藏著所有答案的起點。我在2018年第一次部署這樣的系統(tǒng)時在公司內(nèi)網(wǎng)服務(wù)器上折騰了整整三天只為讓index.jsp里一個img srclogo.png能正確顯示。當(dāng)時覺得挫敗現(xiàn)在回頭看那三天建立的是對HTTP協(xié)議、Servlet生命周期、Tomcat類加載機(jī)制最樸素也最牢固的認(rèn)知。技術(shù)會迭代框架會更替但這份對“請求如何抵達(dá)、數(shù)據(jù)如何流轉(zhuǎn)、錯誤如何浮現(xiàn)”的敬畏與耐心永遠(yuǎn)不會過時。本文還有配套的精品資源點擊獲取