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