ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JavaWeb图书管理系统:Eclipse+Tomcat环境搭建与排错

JavaWeb图书管理系统:Eclipse+Tomcat环境搭建与排错

第二天的工作说白了就一件事:让 JavaWeb 图书管理系统在 Eclipse 里真正跑起来,Tomcat 能启动、页面能打开、控制台不报红。头一天把需求和表结构理顺了,代码一行没写,第二天一上来就要面对环境配置这一关,很多人就是卡在这里放弃的——工程建好了,右键 Run on Server,浏览器弹出来一个 404,然后开始怀疑人生。我这些年带过不少刚接触 Web 开发的同学,第二天的翻车率其实比写业务代码那天还高,因为环境问题给出的报错信息往往极其含糊,一个 404 背后可能是路径、部署、注解、版本四种原因。这篇就把第二天的完整流程拆开讲:从版本对齐、Eclipse 挂 Tomcat、建工程、写第一个 Servlet、部署访问,到各类报错的排查路径,全部是能直接照着复现的操作,也把那些文档里不写、只有踩过才知道的细节一并交代清楚。

1. 第二天到底要交付什么:先把目标钉死

1.1 头一天留下的半成品还缺哪几块

第一天的产出通常是一堆文档:需求清单、表结构、页面草图,外加一份"图书表、读者表、借阅记录表"的字段设计。这些东西在纸面上是完整的,但在工程层面等于零。第二天的验收标准我一般定得非常具体:打开 Eclipse,工程结构正确,Tomcat 能在 Server 视图里启动,浏览器输入一个地址能看到"图书列表"这四个字加两条假数据,控制台没有任何红色堆栈。就这么点事,但它串起了整条链路,链路通了,后面写 DAO、写 JSP 只是往这个骨架里填肉。

为什么要把标准定得这么死?因为 Web 开发最容易出现"局部正确、整体不通"的状态。你的 Servlet 代码写得再漂亮,如果类没编译进build/classes,或者WEB-INF/lib里缺了驱动包,容器一样给你 500。第二天的核心价值不是产出多少代码,而是把"代码到容器到浏览器"这条通路验证一遍,后面每加一个功能都能立刻看到效果,反馈循环就建立起来了。反过来,通路没打通就闷头写三天业务,第四天集中调试,那才是灾难。

我在实际操作里会给自己定一个"最小可运行切片":一个 Servlet,硬编码两条图书记录,输出一段 HTML。不连数据库,不引任何第三方包。这样任何报错都只可能来自环境本身,排查范围极小。等这个切片跑通,再加 JDBC,再加连接池,出问题就能立刻定位到"是新加的东西引入的",而不是在二十个变量里猜。

1.2 为什么这套老组合还在被拿来练手

现在新项目很少这么起了,但教学和练手场景里,Eclipse 加 Tomcat 依然是最合适的组合之一。原因不复杂:Tomcat 是一个纯粹的 Servlet 容器,不做太多魔法,你写的 Servlet 怎么被加载、web.xml怎么被解析、WEB-INF为什么不能被浏览器直接访问,这些机制它都摆在明面上。用重量级框架起步,这些东西全被封装掉了,出了问题只能靠猜。图书管理系统这种规模的练手项目,正好需要你把"请求进来、容器分发、Servlet 处理、响应回去"这条链走一遍,走通了,后面理解任何框架的过滤器、拦截器、路由都有底子。

另一个现实原因是可控性。Tomcat 的目录结构简单到你能手动改配置文件、手动扔 war 包、手动看日志,logs目录下的catalina.out和localhost.log里写得明明白白。我在排查部署问题时最常用的手段就是直接去看wtpwebapps下面到底有没有我的 class 文件,这个动作在封装程度高的环境里根本做不了。所以第二天坚持用这套组合把地基打牢,我认为是划算的,哪怕后面要换。

1.3 三个版本号必须对得上,这是后面所有问题的源头

新手最容易忽略的就是版本。我这里说的不是"越新越好",而是三者之间的兼容关系。Servlet 规范在 Tomcat 10 做了一次大迁移,包名从javax.servlet换成了jakarta.servlet,这个变化意味着你在网上抄的任何一段import javax.servlet.http.HttpServlet;在 Tomcat 10 上都会直接编译不过。很多人第二天的第一个 500 就是这么来的。

下面这张表是我自己整理的对齐关系,照着选基本不会出问题:

组件推荐版本关键约束踩坑说明
JDK8 或 11与 Tomcat 大版本匹配,编译级别一致JDK 17 配 Tomcat 8.5 会出现反射相关告警甚至启动失败
Tomcat9.0.x使用javax.servlet包名生态资料最多,教程基本都基于它
Tomcat10.1.x使用jakarta.servlet包名抄旧代码必改 import,web.xml命名空间也要换
Eclipse带 Enterprise Java and Web 的版本必须含 WTP 插件标准版没有 Dynamic Web Project 选项
Servlet 规范4.0(配 Tomcat 9)与web.xml的version属性对应版本写错会导致注解扫描失效

我的建议很直接:练手阶段就用 JDK 8 或 11 加 Tomcat 9。不是因为新版本不好,而是因为你在搜索框里敲进去的每一个报错,九成以上的答案都基于这个组合,能省掉大量辨别成本。等你把整条链路摸透了,再迁移到新版本,那时候你已经有能力自己看官方迁移说明了。

2. 环境搭建:把 Eclipse 和 Tomcat 接上

2.1 安装路径的选择比想象中重要

装 JDK 和 Tomcat 的时候,路径里不要有空格,不要有中文,不要有括号。这条规则听起来像是老生常谈,但它是真实存在的故障源。Program Files带空格,某些脚本和启动参数在拼接时会被截断;中文路径在某些编码环境下会变成乱码目录名,导致 Tomcat 找不到CATALINA_HOME。我自己的习惯是统一放在一个短路径下,比如D:\dev\jdk和D:\dev\tomcat9,一眼就能看清。

环境变量按老规矩配:JAVA_HOME指向 JDK 根目录(不是bin),CATALINA_HOME指向 Tomcat 根目录,然后把%JAVA_HOME%\bin和%CATALINA_HOME%\bin加进Path。配完必须验证,不能想当然:

java -version javac -version

两个命令输出的版本号必须一致。我见过太多次java -version显示 1.8 而javac -version显示 17 的情况,原因是机器上装了两套 JDK,Path里顺序错了。这种状态下 Eclipse 编译用的是 17,Tomcat 运行用的是 8,出现的错误会非常诡异,比如编译通过但运行时报UnsupportedClassVersionError。这一类问题只要一开始花两分钟验证,后面能省几个小时。

顺手把 Tomcat 单独启动一次,不通过 Eclipse,直接双击bin\startup.bat。窗口里刷出一堆日志,最后停在Server startup in xxx ms,浏览器打开http://localhost:8080能看到默认页,说明 Tomcat 本身没问题。这一步的意义在于把"Tomcat 能不能跑"和"Eclipse 配得对不对"两个问题拆开,出问题时你能立刻判断是哪一层的锅。看完记得用shutdown.bat关掉,否则后面 Eclipse 启动时会报端口占用。

2.2 在 Eclipse 里挂载运行时环境

打开 Eclipse,先确认视图里有 Server 面板。没有的话从Window > Show View > Other里搜 Servers 调出来。然后在面板空白处右键New > Server,展开目录选 Tomcat 对应的版本,下一步会让你指定 Tomcat 的安装目录,选到D:\dev\tomcat9这一层,JRE 选你配好的那套 JDK。Eclipse 会自动识别CATALINA_HOME下的配置,点 Finish。

这一步有两个细节值得说。第一,Server 面板里创建的这个"服务器实例",本质上是 Eclipse 在 workspace 的.metadata目录下复制了一份 Tomcat 配置,它是一个虚拟的服务器定义,不是你磁盘上那个 Tomcat 目录的实时映射。第二,双击这个服务器实例,会打开一个配置页面,里面有个Server Locations区域,默认选的是Use workspace metadata,部署路径指向.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps。这个路径记住了,后面排查部署问题全靠它。

如果项目跑起来发现改了代码不生效,或者想直接看容器里到底有什么,把这里改成Use Tomcat installation,部署路径就变成 Tomcat 安装目录下的webapps,所见即所得。反过来,用 metadata 模式的好处是多个工程互不干扰,也不会污染 Tomcat 本体。我个人的用法是:开发阶段保持默认,遇到"明明改了却没生效"的怪问题时临时切过去看一眼,看完再切回来。

还有一点,配置页面里的Ports部分能看到 HTTP/1.1 端口,默认 8080。如果这台机器上已经有别的东西占了 8080(开发机上太常见了),在这里改成 8081 就行,改完记得访问地址也跟着换。别去手动改 Tomcat 目录下的server.xml,因为 Eclipse 用的是它自己那份副本,改原文件不生效,很多人在这上面浪费过时间。

2.3 目录结构与编译输出路径必须心里有数

工程建好之后,Package Explorer 里会看到这么几个节点:src、JRE System Library、Java Resources、WebContent(新版本 Eclipse 里叫src/main/webapp或者直接显示为Deployment Descriptor附近的WebContent)。这里最容易混淆的是"源码在哪"和"编译产物在哪"。

标准的 Dynamic Web Project 结构是这样的:Java 源码放在src,编译后输出到build/classes;JSP、HTML、CSS 这些静态和动态页面放在WebContent下;第三方 jar 包放WebContent/WEB-INF/lib;WebContent/WEB-INF/web.xml是部署描述文件。关键点在于:WebContent下的东西会被原样拷贝到部署目录,而src下的.java文件会被编译成.class后放进WEB-INF/classes,源码本身不会进部署目录。

这个映射关系决定了三个高频故障。一是"类找不到":WebContent/WEB-INF/classes下没有对应的 class 文件,说明没编译成功,去看 Problems 视图或者build/classes目录是不是空的。二是"jar 包不生效":你把 jar 拖到了工程根目录而不是WEB-INF/lib,编译期能识别但运行期容器找不到,因为容器的类加载器只扫WEB-INF/lib和WEB-INF/classes。三是"页面 404":HTML 放错了位置,放到WEB-INF里面去了,那个目录是受保护的,浏览器永远访问不到。

提示:WEB-INF目录是容器的保护区域,里面的任何资源都不能通过 URL 直接访问,只能由服务端转发。JSP 放在这里反而是一种安全实践,但静态页面放进去就等着 404 吧。

顺便把源码编码固定下来。Window > Preferences > General > Workspace里的 Text file encoding 设成 UTF-8,Java > Compiler里的编译级别和 JRE 版本对齐。这一步现在花两分钟,能避免后面大量的中文乱码问题,具体原因在第 5 节展开。

3. 建工程:从 Dynamic Web Project 到第一个 Servlet

3.1 创建工程时那几个勾选项的真正含义

File > New > Dynamic Web Project,第一页填工程名,比如BookManager,Target runtime 选刚才挂的 Tomcat 9,Dynamic web module version 选 4.0。这个版本号不是随便选的,它对应web.xml里的version属性,也决定了容器怎么解析配置。选 3.1 和选 4.0 在功能上差别不大,但两者与 Tomcat 9 的匹配度都很好;如果误选了 5.0,而 Tomcat 是 9,就会在部署时报规范不支持的警告。

第二页的 Source folders 和 Context root 值得看一眼。Context root 默认等于工程名,这意味着你的访问地址前缀是/BookManager。我建议保持默认,别改成一个看不出所以然的名字,因为后面所有路径拼接都要用到它,改错一次就是一连串 404。Content directory 默认WebContent,保持。

点 Finish 之后,如果弹出问你要不要生成web.xml,一定要选生成。虽然 Servlet 3.0 之后可以用@WebServlet注解代替配置,但初学阶段把web.xml留着,你能直观看到 URL 是怎么映射到类的,出问题时也有一个明确的排查入口。生成的web.xml大概是这个样子:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <display-name>BookManager</display-name> </web-app>

如果这个文件打开是空白的或者报 schema 相关错误,说明网络环境拿不到 xsd 校验,不影响运行,可以忽略,但 XML 头部声明一定要完整,否则容器解析时可能直接抛异常。

3.2 web.xml 配置与注解方式,先弄清各自的适用场景

图书管理系统的第二天,我一般会让两种方式都跑一遍,目的是建立直观认知。web.xml方式长这样:

<servlet> <servlet-name>BookListServlet</servlet-name> <servlet-class>com.demo.book.web.BookListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>BookListServlet</servlet-name> <url-pattern>/book/list</url-pattern> </servlet-mapping>

注解方式则把这两段揉进类上:

@WebServlet(name = "bookList", urlPatterns = "/book/list") public class BookListServlet extends HttpServlet { // ... }

两种方式有个必须知道的冲突规则:如果同一个 Servlet 既在web.xml里配置了,类上又加了注解,注解会被忽略,以配置文件为准(这一行为在不同容器版本上略有差异,稳妥做法是只用一种)。我见过有同学改了半天注解里的路径不生效,最后发现web.xml里还留着一段旧配置,两个url-pattern打架。

实际选型上,我倾向用注解,理由是可维护性好,改路径不用在两个文件之间跳。但有一种情况必须用web.xml:需要配置<welcome-file-list>、<filter>的顺序、<error-page>这些全局元素的时候。图书管理系统后面大概率要做登录拦截、字符编码过滤器,那时候web.xml还是要回来改的,所以两种都熟悉一下没坏处。

3.3 第一个 Servlet 与图书列表页的实操

在src下建包com.demo.book.web,新建一个类继承HttpServlet,重写doGet。这个阶段先不连数据库,把数据硬编码在内存里,目的只有一个:验证请求能进来、响应能出去。代码大概是这样:

package com.demo.book.web; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/book/list") public class BookListServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); req.setCharacterEncoding("UTF-8"); List<String[]> books = Arrays.asList( new String[]{"1", "深入理解计算机系统", "技术类", "在架"}, new String[]{"2", "长日将尽", "文学类", "借出"} ); PrintWriter out = resp.getWriter(); out.println("<!DOCTYPE html><html><head><meta charset='UTF-8'>"); out.println("<title>图书列表</title></head><body>"); out.println("<h2>图书列表(共 " + books.size() + " 条)</h2>"); out.println("<table border='1' cellpadding='8'>"); out.println("<tr><th>编号</th><th>书名</th><th>分类</th><th>状态</th></tr>"); for (String[] b : books) { out.println("<tr>"); for (String cell : b) { out.println("<td>" + cell + "</td>"); } out.println("</tr>"); } out.println("</table></body></html>"); out.flush(); } }

几个地方值得单独说。resp.setContentType("text/html;charset=UTF-8")必须写在getWriter()之前,因为一旦拿到 Writer,响应头就锁定了,后面再设字符集不生效,中文就成问号。serialVersionUID不写虽然能跑,但编译器会给黄色警告,养成写上的习惯。包名里带web是为了以后和dao、service分层,第二天就把分层骨架立起来,比后面重构省事。

还有一种写法是用 JSP 做展示、Servlet 只负责取数据然后forward。图书管理系统最终肯定走这条路,但第二天我建议先用PrintWriter直出一次,你能清楚看到响应体的每一个字节是怎么产生的,理解"Servlet 就是一个输出流的包装"这件事。等这个通了,再换成 JSP,你会立刻明白 JSP 本质上是容器帮你把 Servlet 写好了。

页面能出来之后,可以再往下走一小步:建一个Book实体类,把String[]换掉。这一步改动很小,但能让后面的 DAO 层有个明确的类型,不用对着下标的b[1]猜哪个字段是书名。第二天的进度到这里我觉得就够满了,再往下就该连数据库了。

4. 部署与访问路径:最容易翻车的一段

4.1 Server 视图的部署流程与三种启动方式

工程建好了,代码写了,真正把它"送进"Tomcat 有两种方式。第一种是拖拽:在 Servers 视图里右键服务器实例选Add and Remove,把工程挪到右侧的 Configured 列表里。第二种是直接右键工程Run As > Run on Server,Eclipse 自动帮你完成添加和部署。新手用第二种更快,但第一种你能清楚看到"哪些工程被部署了",多工程场景下更好管理。

启动服务器有三种状态要分清:Start 是正常启动,会加载所有已配置的工程;Debug 是以调试模式启动,可以打断点、看变量,步骤和 Start 一样只是多了 JPDA 监听;Start 旁边的Restart和Publish则用于重新发布。我踩过最多的坑是:改了代码之后不重启也不重新发布,直接刷新浏览器,然后惊讶地发现没生效。原因是 Eclipse 的自动发布有延迟,而且某些改动(比如web.xml、注解路径)必须重启服务器才生效,热替换只对方法体内部的改动有效。

判断有没有发布成功,最快的办法是去部署目录看一眼。前面说过路径在.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps,进去应该能看到一个以BookManager命名的文件夹,里面有WEB-INF、META-INF,WEB-INF下又有classes\com\demo\book\web\BookListServlet.class。如果classes是空的或者不存在,那说明编译都没过,问题在 Java Build Path,不在部署。

4.2 URL 怎么拼:一个字符都不能错

这是第二天 404 的头号成因。完整的访问地址由三段组成:

http://localhost:8080 / 上下文路径 / Servlet 映射路径 http://localhost:8080 / BookManager / book/list

三段里任何一段错一个字符,就是 404。上下文路径默认等于工程名BookManager,大小写敏感;Servlet 映射路径是注解里写的/book/list,开头必须有斜杠,末尾不能有多余斜杠。我见过写成book/list(少斜杠)、/book/list/(多斜杠)、/BookManager/book/list(重复拼接)的各种版本。更隐蔽的一种是工程名改了但 Server 里还挂着旧的部署声明,导致上下文路径和你想的不一样。

排查这类问题的标准动作是:直接访问http://localhost:8080/BookManager/,看能不能出来欢迎页。如果这一步就 404,说明上下文路径不对或者工程没部署;如果这一步正常,只有具体路径 404,说明是 Servlet 映射的问题。把问题二分,效率立刻上来了。

再补一个容易忽略的点:web.xml里如果配了<welcome-file-list>指向index.jsp,但你把index.jsp建在了WEB-INF下,那么访问根路径也会 404,因为容器找不到可用的欢迎文件,且受保护目录不能被直接请求。欢迎文件的位置必须在WebContent根下。

4.3 什么时候该 Clean,什么时候不用

Project > Clean这个动作在 Eclipse 里被滥用了,很多人一遇到问题就先 Clean。它实际做的事是删掉build目录重新编译所有源码,代价是几秒到几十秒的等待。真正需要 Clean 的场景其实有限:删除过源文件但build/classes里还残留旧 class、改了包名目录结构却没有同步、第三方 jar 换版本后编译缓存不对。

不需要 Clean 的场景:刚改了一个方法内部的逻辑,只需要重启服务器;刚改了一个 JSP,只需要刷新浏览器(JSP 会被容器重新编译);刚改了web.xml,重启服务器即可。频繁 Clean 除了浪费时间没别的效果,而且如果编译真有错误,Clean 只会让你多等一遍再看到同样的红叉。

我自己的习惯是:改代码之后先看 Problems 视图有没有红叉,没有就直接重启服务器验证。连续两次不生效,才去看部署目录里的 class 时间戳对不对,最后才考虑 Clean。这个顺序能覆盖绝大多数情况,也比无脑 Clean 快得多。

5. 报错排查实录:第二天最常撞上的几堵墙

5.1 启动阶段:端口被占和 JVM 参数

服务器起不来,控制台第一行经常是Port 8080 required by Tomcat v9.0 Server at localhost is already in use。原因可能是上一次的 Tomcat 没关干净,也可能是别的程序占了端口。排查命令:

netstat -ano | findstr :8080

拿到最后一列的 PID,再执行:

taskkill /F /PID 12345

杀完再启动。如果不想杀,就在 Server 配置页把端口改成 8081,访问地址同步改。另一种情况是 Eclipse 自己的进程卡死了,任务管理器里能看到残留的javaw.exe,直接结束进程比在 Eclipse 里反复重启更干脆。

还有一类启动报错跟 JVM 有关,比如java.lang.OutOfMemoryError: Metaspace,说明类加载的元空间不够。在 Server 配置页打开Open launch configuration,切到 Arguments 标签,在 VM arguments 里加-Xms256m -Xmx512m -XX:MaxMetaspaceSize=256m。这几个参数的含义分别是堆初始值、堆最大值、元空间上限。开发阶段给这么大完全够用,不要一上来就设成几个 G,机器扛不住还拖慢启动。

5.2 访问阶段:404 与 500 的区分思路

这两种错误性质完全不同,但新手常常混为一谈。404 是"找不到资源",说明请求根本没打到你的 Servlet 上,问题在路径层;500 是"服务器内部错误",说明请求打到了,代码执行过程中抛了异常,问题在代码层。判断方法很简单:看控制台有没有堆栈。有堆栈就是 500,没堆栈就是 404。

404 的排查顺序我固定按这个来:先确认服务器确实起来了(浏览器访问http://localhost:8080);再确认工程部署了(Add and Remove里在右侧列表);再确认上下文路径(默认等于工程名,区分大小写);最后确认映射路径(注解或web.xml里的url-pattern)。四级下来基本能定位。

500 的排查主要看堆栈的第一行Caused by。常见的几个:NullPointerException一般是某个对象没初始化,比如req.getParameter()拿到 null 就直接调方法;ClassNotFoundException多半是 jar 没放进WEB-INF/lib;NoSuchMethodError是 jar 版本冲突,同一个类的不同版本被同时加载了。

5.3 中文乱码:三种位置,三种治法

乱码在图书管理系统里几乎必然出现,因为书名全是中文。这里要分清乱码出现在哪一层:控制台输出乱码、页面显示乱码、数据库存取乱码。三种的成因完全不同。

控制台乱码通常是 Eclipse 的 Console 编码和 JVM 的输出编码不一致。改法在Run Configurations > Common > Encoding设成 UTF-8,或者在 VM arguments 里加-Dfile.encoding=UTF-8。注意这是控制台显示问题,数据本身可能是对的。

页面乱码要检查两处:Servlet 里resp.setContentType("text/html;charset=UTF-8")有没有写在getWriter()之前,以及 HTML 的<meta charset='UTF-8'>有没有。这两处任意一处缺失都会让浏览器用错误的编码解析字节流。如果是 POST 提交的中文参数变乱码,还要加req.setCharacterEncoding("UTF-8"),而且这行必须写在第一次调用getParameter之前,否则无效。

数据库乱码是第三层,涉及连接串里的characterEncoding参数和表的字符集。这一块第二天还没碰到,但提前说一句:建库建表时统一用utf8mb4,连接串里带上useUnicode=true&characterEncoding=UTF-8,能省掉后面九成的麻烦。

5.4 一张速查表,出问题先对一遍

下面这张表是我这几年攒下来的,第二天遇到问题我基本先扫一眼这里:

现象最可能的原因快速验证处理方式
启动报端口占用8080 被旧进程占用netstat -ano查 PID结束进程或改端口
没有 Dynamic Web Project 选项Eclipse 是标准版,缺 WTP看 New 菜单有没有 Web 分类换企业版或装插件
访问根路径 404工程未部署或上下文路径不符看Add and Remove右侧列表重新添加工程
访问 Servlet 路径 404映射路径拼错对照注解和地址栏逐字符核对,注意斜杠
页面 500 且堆栈含 ClassNotFoundjar 位置放错看WEB-INF/lib有无该 jar移到 lib 下并重新发布
500 且堆栈含 NullPointer参数或对象未判空看堆栈行号对应的变量加判空和默认值
中文显示为问号响应头字符集缺失看响应头 Content-Type在 getWriter 前设置
POST 参数乱码请求编码未设打印参数原始字节首行加 setCharacterEncoding
改了代码不生效未重新发布或未重启看部署目录 class 时间戳重启服务器或 Publish
控制台中文乱码控制台编码非 UTF-8其他英文输出是否正常改 Run Configuration 编码

注意:表里的"快速验证"一列是重点。排查的核心不是猜原因,而是用最小成本把可能性砍掉一半。养成先验证再动手的习惯,比记住一百个结论都有用。

6. 几个只有踩过才知道的细节

工作空间的路径不要放在中文目录或者带空格的路径下,这一点前面提过,但它引发的问题往往不是直接报错,而是某些资源加载失败后给出莫名其妙的提示。我遇到过一次web.xml明明存在却提示找不到,最后发现是工程所在路径里有中文,容器解析路径时截断了。挪到纯英文短路径下立刻正常。这种问题排查起来极其耗时,因为报错信息完全指向错误的方向。

工程拷贝这件事也值得说。把别人的BookManager整个文件夹拷到自己 workspace 里,直接Import > Existing Projects into Workspace,经常会遇到唐突的红叉,原因是.project和.classpath里记录的还是对方的 Tomcat 运行时名称和 JDK 版本。稳妥做法是新建一个同名工程,把src、WebContent、web.xml手工搬过去,让 Eclipse 重新生成元数据。多花五分钟,少折腾两小时。

另外,养成看logs目录的习惯。Eclipse 的 Console 视图只显示一部分输出,容器完整的启动日志、部署日志、异常堆栈在 Tomcat 的logs目录下,localhost.yyyy-mm-dd.log里通常能找到 Console 里被截断的关键信息。我在排查部署失败时,第一反应就是去翻这个文件,比在 Console 里翻滚动条快得多。

最后说一个心态上的事。第二天的环境搭建确实枯燥,而且反馈来得慢——你敲了一堆命令、点了十几个对话框,最后只是看到一个表格页面。但这一天的投入产出比其实是最高的,因为后面每一行代码的调试都依赖这条通路。我的建议是,通路上任何一步不确定,就停下来验证清楚再往下走,不要带着"应该没问题吧"的假设继续。Web 开发里没有"应该",只有"我验证过了"。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进