
寫(xiě)這套教程的起因很簡(jiǎn)單不少剛接觸 Java Web 的朋友卡在第一步就備受挫折——Tomcat 下載好了IDEA 也裝好了可項(xiàng)目就是起不來(lái)Servlet 明明寫(xiě)了卻總是 404。我做過(guò)幾年一線開(kāi)發(fā)也帶過(guò)新人很確定這些問(wèn)題的根子基本不在代碼而在配置鏈路的某個(gè)細(xì)節(jié)上。所以這篇我會(huì)用 IDEA 2024 最新版把“從 0 到 1 部署 Tomcat 添加 Servlet”這條路完整走一遍涉及到版本選型、環(huán)境變量、IDEA 集成、項(xiàng)目創(chuàng)建、Servlet 編寫(xiě)和調(diào)試排坑照著做就行。1. 項(xiàng)目概述先搞清楚 Tomcat 和 Servlet 各管什么事1.1 Tomcat 和 Servlet 是什么它們是什么關(guān)系Tomcat 由 Apache 軟件基金會(huì)維護(hù)是一個(gè)開(kāi)源的 Servlet 容器和 Java Web 服務(wù)器。它可以獨(dú)立運(yùn)行接收 HTTP 請(qǐng)求并按規(guī)則返回響應(yīng)是絕大多數(shù) Java Web 初學(xué)者的第一個(gè)運(yùn)行環(huán)境。Servlet 則是一段運(yùn)行在服務(wù)器端的 Java 類專門用來(lái)處理客戶端請(qǐng)求并生成動(dòng)態(tài)響應(yīng)。打個(gè)比方Tomcat 是前臺(tái)的“接待員”負(fù)責(zé)收件、拆信封、按地址找人Servlet 是后場(chǎng)的“業(yè)務(wù)員”負(fù)責(zé)真正處理事情再把結(jié)果交給前臺(tái)發(fā)回去。瀏覽器發(fā)來(lái)的每一個(gè)請(qǐng)求都由 Tomcat 解析成 HttpServletRequest找到對(duì)應(yīng)的 Servlet調(diào)用它的方法然后拿到 HttpServletResponse 寫(xiě)回瀏覽器。這個(gè)分工決定了我們的學(xué)習(xí)路徑先讓前臺(tái)開(kāi)工Tomcat 跑起來(lái)再讓業(yè)務(wù)員上崗編寫(xiě)并注冊(cè) Servlet。兩者缺一不可很多人學(xué) Servlet 時(shí)感覺(jué)聽(tīng)不懂說(shuō)白了就是沒(méi)搞明白自己的代碼是怎么被容器“釣”出來(lái)的。1.2 IDEA 2024 環(huán)境下的整個(gè)部署鏈路IDEA 2024 對(duì) Java Web 開(kāi)發(fā)的支持相當(dāng)成熟從工程創(chuàng)建、依賴管理、Tomcat 集成到熱部署調(diào)試基本做到了圖形化操作。但正因?yàn)?IDE 自動(dòng)化程度高新人反而容易忽略背后的東西IDEA 只是幫你把部署包推送到 Tomcat 的 webapps 目錄本質(zhì)跟手動(dòng)復(fù)制 war 包沒(méi)有區(qū)別。整個(gè)鏈路是源碼編譯為 class 文件按 Java Web 規(guī)范打包成 war/exploded 結(jié)構(gòu)Tomcat 啟動(dòng)時(shí)加載對(duì)應(yīng)目錄讀取注解或 web.xml 完成 Servlet 注冊(cè)最后對(duì)外提供 HTTP 服務(wù)。這篇教程會(huì)按這條鏈路逐步落地每一步都講清“為什么這么配”。1.3 版本選型最容易被老教程坑的地方Tomcat 從 10.0 開(kāi)始做了一個(gè)影響面極廣的改動(dòng)Java EE 時(shí)代的javax.servlet.*包名全面遷移為 Jakarta EE 時(shí)代的jakarta.servlet.*。也就是說(shuō)網(wǎng)上大量老教程里import javax.servlet.http.HttpServlet的寫(xiě)法在 Tomcat 10 上面是編譯不過(guò)的或者運(yùn)行時(shí)報(bào)ClassNotFoundException。我這篇默認(rèn)使用 Tomcat 10.1.x 版本Servlet 使用 Jakarta 命名空間因?yàn)檫@是目前最主流的新環(huán)境配置方式。如果你公司項(xiàng)目還在用 Tomcat 9對(duì)應(yīng)使用javax.servlet即可流程基本一致只是要特別注意包名差異。選型時(shí)不要盲目下最新版本先確認(rèn)你的 JDK 版本、現(xiàn)有依賴和你所參考的教程是否匹配。2. 環(huán)境準(zhǔn)備JDK 與 Tomcat 的下載、安裝和驗(yàn)證2.1 下載 Tomcatzip 版還是安裝版去 Apache Tomcat 官網(wǎng)下載時(shí)Windows 下會(huì)看到 32-bit/64-bit Windows Service Installer 和 Core 壓縮包zip兩種主流選擇。我建議下載 zip 版本因?yàn)樗_(kāi)箱即用、不污染系統(tǒng)服務(wù)列表卸載也方便只要?jiǎng)h目錄就行。安裝版會(huì)把 Tomcat 注冊(cè)成 Windows 服務(wù)雖然開(kāi)機(jī)自啟省事但對(duì)學(xué)習(xí)階段反而是干擾。Tomcat 10.1 需要 JDK 11 及以上我用的是 JDK 17這是當(dāng)前支持周期和生態(tài)都比較平衡的版本。下載完成后將壓縮包解壓到一個(gè)不含中文和空格的路徑下例如D:\apache-tomcat-10.1.33。不要放在C:\Program Files這類帶空格的路徑否則后續(xù)腳本解析變量時(shí)極容易出莫名其妙的路徑錯(cuò)誤。2.2 目錄結(jié)構(gòu)快速熟悉解壓后你會(huì)看到如下目錄這里挑幾個(gè)必須認(rèn)識(shí)的bin 存放啟動(dòng)、關(guān)閉腳本startup.bat / shutdown.bat conf 核心配置文件目錄server.xml、web.xml、tomcat-users.xml lib Tomcat 自身依賴的 jar 包 logs 運(yùn)行日志目錄排錯(cuò)最??吹牡胤?webapps 部署目錄war 包或解壓后的項(xiàng)目丟到這里就能被加載 work JSP 編譯后的 class 文件臨時(shí)目錄剛開(kāi)始不用逐個(gè)鉆只要記住代碼部署到 webapps日志看 logs端口和虛擬主機(jī)配置在 conf/server.xml。我見(jiàn)過(guò)有人為了“部署”到處找地方最后才發(fā)現(xiàn) webapps 這層的作用這就是對(duì)目錄結(jié)構(gòu)不熟導(dǎo)致的。2.3 配置 CATALINA_HOME 環(huán)境變量Tomcat 本身不強(qiáng)制要求配置環(huán)境變量在 bin 目錄下雙擊 startup.bat 也能啟動(dòng)。但為了讓 IDEA 能準(zhǔn)確識(shí)別 Tomcat也為了方便在任意目錄通過(guò)命令行使用腳本建議還是配置一下新建系統(tǒng)變量變量名CATALINA_HOME變量值為你的 Tomcat 解壓目錄例如D:\apache-tomcat-10.1.33。在系統(tǒng)變量Path中添加%CATALINA_HOME%\bin。檢查JAVA_HOME是否已正確設(shè)置指向 JDK 安裝目錄。因?yàn)?Tomcat 啟動(dòng)腳本需要找 java 命令找不到會(huì)直接報(bào)JAVA_HOME相關(guān)錯(cuò)誤。配置完環(huán)境變量后命令行執(zhí)行echo %CATALINA_HOME%能正確顯示目錄就說(shuō)明變量生效了。注意修改環(huán)境變量后要重新打開(kāi)命令行窗口或重啟 IDEA否則新值不會(huì)加載。2.4 第一次啟動(dòng)驗(yàn)證啟動(dòng)前先確認(rèn) 8080 端口有沒(méi)有被占用netstat -ano | findstr 8080如果有結(jié)果說(shuō)明端口被占可以后續(xù)改端口或者先排查占用程序。確認(rèn)端口空閑后進(jìn)入 bin 目錄雙擊startup.bat看到類似這樣的日志基本就成功了Server startup in [1234] milliseconds然后瀏覽器訪問(wèn)http://localhost:8080看到 Tomcat 首頁(yè)就算環(huán)境通了。如果控制臺(tái)輸出亂碼大部分是編碼問(wèn)題可以在 log 輸出的 cmd 窗口執(zhí)行chcp 65001臨時(shí)切到 UTF-8 編碼或者在 conf/logging.properties 里調(diào)整字符編碼。這一步卡住的人很多但基本都不涉及代碼問(wèn)題多查端口和 JAVA_HOME 就能解決。3. IDEA 2024 集成 Tomcat創(chuàng)建項(xiàng)目與運(yùn)行配置3.1 版本區(qū)別Community 與 Ultimate 的關(guān)鍵差異IDEA 分為 Community社區(qū)版免費(fèi)和 Ultimate旗艦版付費(fèi)兩個(gè)版本。社區(qū)版不是不能做 Web 開(kāi)發(fā)而是缺少對(duì)“應(yīng)用服務(wù)器”的圖形化集成支持IDEA Ultimate 中可以直接在 Settings 里配置 Tomcat Application Server而社區(qū)版沒(méi)有這個(gè)面板。如果你用的社區(qū)版有兩種選擇一是自己手動(dòng)完成編譯、打包、復(fù)制到 webapps 的流程這對(duì)理解底層機(jī)制很有幫助但不方便二是使用 Ultimate這是大多數(shù)團(tuán)隊(duì)的實(shí)際選擇。下面操作均以 IDEA 2024 Ultimate 為準(zhǔn)你打開(kāi) Settings 后如果找不到 Application Servers 選項(xiàng)就要先確認(rèn)是不是版本問(wèn)題。3.2 讓 IDEA 識(shí)別 TomcatApplication Servers 配置打開(kāi) IDEA進(jìn)入菜單File Settings Build, Execution, Deployment Application Servers點(diǎn)擊加號(hào)選擇 Tomcat Server在Tomcat Home處選擇你本地的 Tomcat 解壓目錄。IDEA 會(huì)自動(dòng)識(shí)別版本并提示缺失依賴一般直接 OK 即可。這一步不需要手動(dòng)填太多東西但有個(gè)小細(xì)節(jié)復(fù)制 Tomcat 目錄路徑時(shí)一定要選到那一層apache-tomcat-10.1.x文件夾不要多選到 bin 目錄也不要少選到外層某個(gè)父目錄。IDEA 要從該目錄下找 lib/catalina.jar 等核心文件路徑錯(cuò)了表面看不出來(lái)運(yùn)行時(shí)會(huì)報(bào)各種找不到類的錯(cuò)誤。3.3 創(chuàng)建 Maven Web 項(xiàng)目既然要寫(xiě) Servlet理論上手工創(chuàng)建普通 Java 項(xiàng)目再引入 servlet-api 也行但日常開(kāi)發(fā)中 Maven 幾乎成了標(biāo)配所以我直接用 Maven 工程來(lái)講也順便解決依賴管理問(wèn)題。新項(xiàng)目流程File New Project選擇 Maven不選自帶骨架。設(shè)置好 GroupId、ArtifactId 后在項(xiàng)目里手工補(bǔ)出 Web 目錄結(jié)構(gòu)。標(biāo)準(zhǔn) Java Web 目錄長(zhǎng)這樣src/main/java Java 源碼 src/main/resources 資源文件配置文件、日志配置等 src/main/webapp Web 根目錄 src/main/webapp/WEB-INF/web.xml 部署描述文件可選在pom.xml里添加 Servlet API 依賴。Tomcat 10.1 對(duì)應(yīng) Jakarta Servlet 5.0/6.0 規(guī)范我這里以 6.0 為例dependencies dependency groupIdjakarta.servlet/groupId artifactIdjakarta.servlet-api/artifactId version6.0.0/version scopeprovided/scope /dependency /dependenciesscope設(shè)置成provided很關(guān)鍵意思是編譯和測(cè)試時(shí)用這個(gè) jar但打包到 war 時(shí)不要包含進(jìn)去因?yàn)?Tomcat 自己已經(jīng)有實(shí)現(xiàn)了。如果漏了這點(diǎn)或者誤設(shè)成 compile部署后雖然不至于立刻報(bào)錯(cuò)但會(huì)導(dǎo)致 jar 包沖突特別是以后引入 spring、攔截器過(guò)濾器時(shí)容易出問(wèn)題。3.4 配置本地 Tomcat 運(yùn)行任務(wù)工程搭好后再配置運(yùn)行任務(wù)。點(diǎn)擊右上角運(yùn)行配置下拉框選擇Edit Configurations新增一個(gè)Tomcat Server Local。在Deployment標(biāo)簽頁(yè)點(diǎn)擊加號(hào)添加Artifact選擇項(xiàng)目的war exploded展開(kāi)的 war 包。這里解釋一下為什么優(yōu)先選war exploded它不壓縮直接以文件夾形式加載到 Tomcat 的 webapps 里IDEA 可以直接關(guān)聯(lián)源碼改代碼后熱更新速度更快。普通war適合交付生產(chǎn)使用本地開(kāi)發(fā)選 exploded 體驗(yàn)更好。配置項(xiàng)里還有一個(gè)Application context默認(rèn)是/項(xiàng)目名這個(gè)就是你的 Web 應(yīng)用訪問(wèn)根路徑。例如這里填/demo那么訪問(wèn) Servlet 的 URL 就是http://localhost:8080/demo/hello。這個(gè)值記牢很多 404 就是因?yàn)樗鼪](méi)找對(duì)。配置完成后點(diǎn)擊運(yùn)行按鈕IDEA 會(huì)自動(dòng)啟動(dòng) Tomcat 并把工程部署進(jìn)去??吹筋愃葡旅娴娜罩揪驼f(shuō)明 IDE 集成沒(méi)問(wèn)題Artifact demo:war exploded: Artifact is being deployed Deployment of web application archive [demo] has finished4. 核心環(huán)節(jié)兩種方式添加 Servlet附完整代碼4.1 用注解配置 Servlet推薦方式Servlet 3.0 以后支持注解不用在 web.xml 里寫(xiě)任何內(nèi)容直接在類上標(biāo)注WebServlet即可。這種方式代碼聚攏、可讀性好我現(xiàn)在開(kāi)發(fā)時(shí)默認(rèn)都用注解。第一步在src/main/java下創(chuàng)建包c(diǎn)om.demo.servlet新建類HelloServlet繼承HttpServletpackage com.demo.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/hello) public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(h1Hello Servlet, IDEA 2024/h1); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }第二步直接重新部署運(yùn)行 TomcatIDEA 中按ShiftF10或用右上角運(yùn)行按鈕瀏覽器訪問(wèn)http://localhost:8080/demo/hello如果能看到Hello Servlet字樣說(shuō)明注解生效了Servlet 已經(jīng)被 Tomcat 加載并注冊(cè)。注意WebServlet(/hello)里的路徑必須帶斜杠而且不要寫(xiě)WebServlet(hello)這種缺斜杠的形式否則映射不生效。第三步解釋幾個(gè)容易踩的坑doGet和doPost要分開(kāi)寫(xiě)還是可以合并看業(yè)務(wù)場(chǎng)景。表單 GET 請(qǐng)求通常走 doGetPOST 表單走 doPost。上面的寫(xiě)法是偷懶把 POST 也丟給 doGet真實(shí)項(xiàng)目中最好分別處理。繼承的extends HttpServlet別寫(xiě)串。Tomcat 10 下一定要 importjakarta.servlet.http.HttpServlet不是javax。resp.getWriter()獲取的是字符輸出流要給瀏覽器返回中文時(shí)一定要先設(shè)置字符編碼這就是為什么我在第一行寫(xiě)了setContentType(text/html;charsetUTF-8)。不加的話中文大概率變成亂碼。4.2 用 web.xml 配置 Servlet傳統(tǒng)方式注解雖然好用但有些老項(xiàng)目、部分中間件或前置過(guò)濾器場(chǎng)景還是要靠 web.xml。另外很多面試題和工作中的老代碼還在用傳統(tǒng)方式所以這塊也必須掌握。項(xiàng)目沒(méi)有 web.xml 時(shí)先手動(dòng)創(chuàng)建src/main/webapp/WEB-INF/web.xml內(nèi)容如下?xml version1.0 encodingUTF-8? web-app xmlnshttps://jakarta.ee/xml/ns/jakartaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttps://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd version5.0 display-namedemo/display-name !-- 聲明 Servlet 類 -- servlet servlet-nameHelloServlet/servlet-name servlet-classcom.demo.servlet.HelloServlet/servlet-class /servlet !-- 配置 URL 映射 -- servlet-mapping servlet-nameHelloServlet/servlet-name url-pattern/hello/url-pattern /servlet-mapping /web-app注意區(qū)分servlet-name在兩個(gè)標(biāo)簽里的作用它只是一個(gè)邏輯名稱作用是把“類定義”和“URL 映射”連接起來(lái)。你完全可以隨便起名但最好跟類名保持一致省得自己看暈。使用傳統(tǒng)方式時(shí)上一個(gè)示例里的WebServlet注解必須刪掉否則同一個(gè) Servlet 被注冊(cè)兩次Tomcat 啟動(dòng)時(shí)會(huì)直接報(bào)重復(fù)映射的異常。servlet-class必須寫(xiě)全限定類名比如com.demo.servlet.HelloServlet不能只寫(xiě)HelloServlet。4.3 注解與 web.xml 的選擇建議從 Servlet 3.0 到現(xiàn)在的 Jakarta EE注解已經(jīng)是絕對(duì)主流它最大的優(yōu)勢(shì)是消除了大量“連接”代碼。web.xml 仍然存在的意義在于沒(méi)有源碼的第三方 Servlet 也可以配置、某些 Servlet 在框架中需要按指定順序?qū)嵗⒉糠诌\(yùn)維場(chǎng)景想在不改代碼的前提下調(diào)整映射。一般現(xiàn)代項(xiàng)目能用注解就絕不開(kāi) web.xml但理解兩種方式背后的機(jī)制對(duì)讀懂像 Spring MVC 的 DispatcherServlet 注冊(cè)邏輯很有幫助。5. 部署運(yùn)行與問(wèn)題排查從啟動(dòng)報(bào)錯(cuò)到 404 定位5.1 部署后到底發(fā)生了什么當(dāng)你點(diǎn)擊 IDEA 的 Run 按鈕IDEA 會(huì)執(zhí)行這樣一串動(dòng)作把工程編譯成 class將 webapp 資源和編譯產(chǎn)出組織成 exploded war 結(jié)構(gòu)復(fù)制到當(dāng)前 Tomcat 實(shí)例關(guān)聯(lián)的部署位置然后啟動(dòng) Tomcat。Tomcat 啟動(dòng)時(shí)掃描對(duì)應(yīng)應(yīng)用的注解或者讀取 WEB-INF/web.xml把 Servlet 注冊(cè)進(jìn)上下文容器。所以部署報(bào)錯(cuò)時(shí)先判斷是哪個(gè)環(huán)節(jié)有問(wèn)題。如果 IDEA 控制臺(tái)只報(bào)“端口占用”那是啟動(dòng)前檢查失敗如果報(bào)“Artifact 部署失敗”多半是構(gòu)建目錄不完整如果 Tomcat 正常啟動(dòng)但訪問(wèn) 404基本就是 Servlet 映射錯(cuò)誤或 context path 不對(duì)。把問(wèn)題定位到階段排查效率會(huì)高很多。5.2 常見(jiàn)問(wèn)題速查表下面這張表是我在實(shí)際帶人過(guò)程中反復(fù)用到的排查清單覆蓋新手最常遇到的場(chǎng)景現(xiàn)象可能原因處理方法啟動(dòng)時(shí)報(bào)Address already in use: JVM_Bind8080 端口被占用換端口或殺掉占用進(jìn)程server.xml修改端口IDEA 中也要同步更新 HTTP port訪問(wèn)報(bào) 404Tomcat 首頁(yè)能開(kāi)Servlet 映射路徑不對(duì)或 context path 不對(duì)確認(rèn)WebServlet/url-pattern核對(duì) Application context 值例如/demo/hello中的/demo來(lái)自部署配置啟動(dòng)報(bào)ClassNotFoundException: javax.servlet...用錯(cuò)命名空間使用了 Tomcat 9 的老代碼將javax替換為jakarta或改用 Tomcat 9啟動(dòng)報(bào)重復(fù)映射注解和 web.xml 同時(shí)注冊(cè)了同一個(gè) Servlet二選一保留一種注冊(cè)方式頁(yè)面中文亂碼沒(méi)有設(shè)置編碼或文件本身編碼不對(duì)resp.setContentType(text/html;charsetUTF-8)IDEA 中 File Encoding 統(tǒng)一為 UTF-8getWriter()報(bào) IOException已有其他輸出流或響應(yīng)已提交不要在一次性響應(yīng)中重復(fù)調(diào)用檢查是否先調(diào)用了getOutputStream()IDEA 控制臺(tái)輸出亂碼控制臺(tái)默認(rèn)編碼不是 UTF-8VM options 加-Dfile.encodingUTF-8或在Help Edit Custom VM Options中顯式設(shè)置無(wú)法修改 Tomcat 端口改了server.xml但 IDEA 配置未同步IDEA 運(yùn)行配置中單獨(dú)維護(hù) HTTP Port 設(shè)置要和 server.xml 保持一致5.3 搞定一堆奇怪報(bào)錯(cuò)后的調(diào)試技巧解決語(yǔ)法和配置問(wèn)題之后真正寫(xiě)業(yè)務(wù)時(shí)最大的需求是調(diào)試。IDEA 里打斷點(diǎn)調(diào)試 Servlet跟調(diào)普通 Java 程序差不多在代碼行號(hào)左側(cè)單擊設(shè)置斷點(diǎn)然后點(diǎn)擊調(diào)試按鈕蟲(chóng)形圖標(biāo)啟動(dòng) Tomcat。當(dāng)瀏覽器請(qǐng)求打到對(duì)應(yīng) Servlet 時(shí)IDE 會(huì)停留在斷點(diǎn)處可以查看 HttpServletRequest 的請(qǐng)求參數(shù)、Header 等。用這種方式能直接看到請(qǐng)求進(jìn)來(lái)了沒(méi)有、走到了哪個(gè)方法、參數(shù)是什么。很多新手寫(xiě) Servlet 一旦 404 就手足無(wú)措其實(shí)只要在doGet第一行打個(gè)斷點(diǎn)然后刷新瀏覽器如果斷點(diǎn)沒(méi)有命中說(shuō)明映射就沒(méi)進(jìn)來(lái)問(wèn)題在 URL 或部署地址如果斷點(diǎn)命中說(shuō)明映射沒(méi)問(wèn)題是業(yè)務(wù)邏輯或響應(yīng)環(huán)節(jié)出錯(cuò)。這一招可以從根源上區(qū)分“請(qǐng)求沒(méi)找到 Servlet”和“Servlet 執(zhí)行出錯(cuò)”兩類問(wèn)題。6. 進(jìn)階經(jīng)驗(yàn)熱部署、中文亂碼和項(xiàng)目擴(kuò)展方向6.1 熱部署與重加載的使用心得IDEA 和 Tomcat 集成后默認(rèn)情況下修改 Java 代碼重新編譯IDEA 會(huì)嘗試熱更新上下文但 Servlet 類這種容器級(jí)對(duì)象的更新經(jīng)常不生效。實(shí)測(cè)最穩(wěn)的方式是修改代碼后如果只是改了 JSP、HTML、CSS 等靜態(tài)資源點(diǎn)瀏覽器刷新就行如果改了 Java 類點(diǎn)運(yùn)行配置里的 update 按鈕或者干脆重啟 Tomcat。不要過(guò)分依賴熱部署尤其在加了 Servlet 注冊(cè)新增WebServlet類后不重啟大概率不生效。把熱部署當(dāng)輔助工具而不是全部學(xué)基礎(chǔ)階段寧可多按幾次重啟也別浪費(fèi)時(shí)間等“不知道有沒(méi)有生效”的狀態(tài)。6.2 中文亂碼的完整解法中文亂碼在 Servlet 學(xué)習(xí)階段幾乎每個(gè)項(xiàng)目都會(huì)遇到。頁(yè)面請(qǐng)求的亂碼分三處瀏覽器發(fā)過(guò)來(lái)的中文參數(shù)、服務(wù)器返回的中文響應(yīng)、以及日志控制臺(tái)的中文輸出。前兩者最常用方案是// 設(shè)置請(qǐng)求編碼處理 POST 請(qǐng)求體中的中文參數(shù) req.setCharacterEncoding(UTF-8); // 設(shè)置響應(yīng)編碼和內(nèi)容類型放最前面 resp.setContentType(text/html;charsetUTF-8);但要注意req.setCharacterEncoding對(duì) GET 請(qǐng)求的 Query String 不一定生效因?yàn)?Query String 的編碼取決于服務(wù)器 URIEncoding。保守的通用做法是在server.xml的 Connector 上增加URIEncodingUTF-8。雖然新版 Tomcat 默認(rèn)已改為 UTF-8但了解這個(gè)原理能幫你排查到很多歷史項(xiàng)目的難題。6.3 從 Servlet 到實(shí)際項(xiàng)目的擴(kuò)展思考很多人學(xué)會(huì) Servlet 后下一步就去擼 MVC 框架反而把這塊基礎(chǔ)丟了。其實(shí) Servlet 是 Spring MVC 的基石DispatcherServlet 本身就是一個(gè) Servlet。建議你在這個(gè)小項(xiàng)目的基礎(chǔ)上自己擴(kuò)展幾個(gè)方向?qū)懸粋€(gè)登錄功能用HttpSession保存用戶狀態(tài)體驗(yàn)會(huì)話管理。寫(xiě)一個(gè)請(qǐng)求轉(zhuǎn)發(fā)和重定向的小例子搞懂forward和redirect的區(qū)別。寫(xiě)一個(gè) Filter把請(qǐng)求日志統(tǒng)一打印出來(lái)理解過(guò)濾器鏈的執(zhí)行順序。后面這幾個(gè)方向都會(huì)回到 Servlet 的底層知識(shí)點(diǎn)上。這篇文章里我沒(méi)有刻意展開(kāi) Servlet 的生命周期原理但實(shí)操中你只要關(guān)注一個(gè)問(wèn)題就夠init只執(zhí)行一次service每次請(qǐng)求都會(huì)執(zhí)行。這個(gè)模型能解釋很多開(kāi)發(fā)中的詭異現(xiàn)象比如全局變量和實(shí)例變量的并發(fā)問(wèn)題。最后分享一個(gè)小技巧每次修改server.xml或web.xml后一定記得看 logs 目錄下的catalina.日期.log。Idea 控制臺(tái)展示的日志做了截?cái)嗵幚砗芏嚯[藏的堆棧異常細(xì)節(jié)只有這個(gè)文件里才有完整記錄。我排除了不少“靈異問(wèn)題”最后都是在這個(gè)文件里找到了真正的 root cause。多花幾十秒看日志比盲目重啟十次管用得多。