
1. 从“404”说起一个资深后端工程师的日常排障“请求的资源不可用”——这句话对任何一个和Tomcat打过交道的开发者来说都再熟悉不过了。它像一个沉默的守门人用冷冰冰的HTTP 404状态码将你挡在应用的大门之外。表面上看这只是服务器找不到你请求的路径但背后隐藏的原因却可能千差万别从部署时一个字母的大小写错误到复杂的Spring MVC拦截器配置甚至是国产化替代过程中的兼容性陷阱。今天我们不谈那些泛泛而谈的“重启大法”或“检查路径”而是深入Tomcat和其上层应用如Spring Boot的肌理系统性地拆解这个“状态报告”背后的每一种可能并给出精准的“手术刀式”解决方案。无论你是刚被404困扰的新手还是在考虑将Tomcat替换为宝蓝德BES等国产中间件的架构师这篇文章都将是你手边最实用的排错指南。2. 核心问题定位404错误的四层诊断模型遇到404盲目尝试是最低效的。我习惯用一个四层模型来快速定位问题从外到内层层递进这能帮你节省大量时间。2.1 第一层网络与访问入口检查这一层排查的是最基础、也最容易被忽略的问题通常发生在项目刚部署或环境变更后。首先确认你的访问入口是否正确。很多新手会混淆几个关键URLTomcat服务器根地址通常是http://localhost:8080。访问这个地址你应该能看到Tomcat的默认欢迎页一只叼着橄榄枝的猫。如果连这个都打不开说明Tomcat服务本身没有成功启动问题可能出在端口占用、环境变量或启动脚本上。你的Web应用上下文路径Context Path这是你的应用在Tomcat中的“挂载点”。如果你将项目打包成myapp.war并直接丢到webapps目录下Tomcat解压后默认的上下文路径就是/myapp。此时你的应用根地址应该是http://localhost:8080/myapp。在Spring Boot中这个路径可以通过server.servlet.context-path/myapp来配置。目标资源的具体路径在应用根地址之后才是你在代码中定义的Controller路径或静态资源路径。例如一个RequestMapping(“/hello”)的控制器完整访问地址是http://localhost:8080/myapp/hello。注意在IDE如IntelliJ IDEA中运行项目时务必要分清“Tomcat服务器配置”中的“部署路径(Deployment)”和“应用上下文(Context)”。IDEA有时会自动生成一个带版本号或特殊字符的上下文路径这会导致你预期的访问地址失效。最佳实践是在IDEA的Tomcat运行配置中将“Application context”明确设置为/或你指定的路径。其次检查浏览器缓存和网络代理。这是一个经典的“坑”。你的代码已经更新但浏览器顽固地显示旧页面的404。按CtrlF5进行强制刷新或打开开发者工具F12在“网络(Network)”选项卡中勾选“禁用缓存(Disable cache)”。另外某些网络环境或本地开发的代理设置如Charles、Fiddler可能会干扰请求暂时关闭它们进行测试。2.2 第二层应用部署与结构验证当入口无误后我们需要确认应用是否被Tomcat正确识别和加载。核心检查点是Tomcat的webapps目录和日志文件。进入Tomcat的webapps目录找到你的应用文件夹或WAR包。对于一个标准的Web应用其内部必须包含WEB-INF目录。WEB-INF下通常要有web.xml: 部署描述符文件。对于Servlet 3.0包括Spring Boot的应用这个文件可能不是必需的但它的存在与否及内容正确性在特定环境下至关重要。classes目录: 存放编译后的Java类文件。lib目录: 存放应用依赖的JAR包。如果WEB-INF目录缺失、结构损坏或者web.xml配置错误例如将Servlet映射到了错误的url-patternTomcat就无法正确初始化你的应用所有请求自然都会404。最关键的证据在日志里。打开Tomcat的logs目录重点查看catalina.out和localhost_yyyy-MM-dd.log文件。在应用启动时你应该能看到类似下面的关键行信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 正在把web应用程序部署到目录 [myapp] ... 信息 [main] org.apache.catalina.core.StandardContext.startInternal 容器[Catalina].[localhost].[/myapp]启动成功如果应用部署失败这里会有明确的错误堆栈信息。如果根本没看到你的应用名相关的部署日志那说明WAR包未被识别或server.xml配置有误。2.3 第三层Servlet容器与请求映射分析这一层深入到请求处理的内部流程。Tomcat作为一个Servlet容器其核心工作是接收HTTP请求并根据配置将其分发给对应的Servlet处理。静态资源404如果你的HTML、图片、CSS/JS文件访问不到问题通常出在静态资源映射上。在传统的web.xml中有一个名为default的Servlet专门处理静态资源。在Spring Boot中静态资源默认放在classpath:/static/,/public/,/resources/,/META-INF/resources/目录下可以通过spring.web.resources.static-locations自定义。常见的坑是自定义了WebMvcConfigurer或拦截器Interceptor后不小心拦截或屏蔽了对静态资源路径的请求。动态请求404Controller不生效这是Spring MVC项目中最常见的情况。原因可能包括Controller未被扫描到确保你的Controller类位于Spring Boot主应用类带SpringBootApplication注解的类的同包或子包下。如果不在你需要使用ComponentScan注解显式指定扫描路径。注解使用错误Controller必须和RequestMapping或其衍生注解GetMapping,PostMapping等配合使用。一个只有Controller而没有映射注解的类不会响应任何请求。请求方法不匹配在浏览器地址栏直接输入URL默认是GET请求。如果你的Controller方法只映射了PostMapping(“/submit”)那么用GET访问自然会404。URL路径拼写错误注意大小写、斜杠和路径参数。/user/info和/user/Info在大多数服务器上是两个不同的路径。2.4 第四层框架特性与高级配置排查对于一些复杂项目或特定技术选择问题可能藏在更深的地方。Spring Boot的“欢迎页”与“错误页”Spring Boot默认对根路径“/”有特殊处理。如果你没有定义处理“/”的控制器它会尝试寻找index.html作为欢迎页。如果连欢迎页都没有你访问“/”可能会得到一个Whitelabel Error Page其中包含错误信息而不是404。但如果你访问的是“/api/xxx”不符合上述规则就会直接404。此外自定义的ErrorController可能会影响404页面的表现形式需要检查其实现逻辑。拦截器Interceptor与过滤器Filter的误杀这是高级bug的温床。你写了一个拦截器本意是拦截/admin/**路径但由于路径模式配置错误如/**导致所有请求都被拦截并中断最终表现为404。务必在拦截器的preHandle方法中加入调试日志确认请求是否通过了拦截链。关于国产中间件替代的思考最近“将Tomcat替换成国产中间件宝蓝德”是一个热门话题。如果你正在做此类迁移并出现了404排查思路需要扩展规范兼容性宝蓝德BES等国产中间件通常宣称兼容Servlet/JSP规范但实现细节上可能存在差异。重点检查web.xml中是否使用了Tomcat特有的配置或标签。部署方式国产中间件的WAR包部署、上下文路径配置、日志查看方式可能与Tomcat不同需要查阅其官方文档。Spring Boot内嵌容器Spring Boot默认内嵌Tomcat。替换为宝蓝德通常意味着要排除Tomcat依赖引入宝蓝德的Starter并可能需要对一些自动配置进行调整。任何在替换过程中遗漏的依赖或配置都可能导致应用上下文初始化失败从而引发404。3. 实战演练系统性解决404问题的操作清单光有理论不够我们通过一个完整的实战流程将上述四层模型付诸实践。3.1 第一步基础环境与部署检查启动验证启动Tomcat访问http://localhost:8080。若无欢迎页检查端口是否被占用netstat -ano | findstr :8080或查看logs/catalina.out的启动错误。部署确认将你的WAR包例如demo.war放入tomcat/webapps/。观察该目录下是否自动生成了demo文件夹。如果没有可能是WAR包损坏或Tomcat的自动部署被禁用检查conf/server.xml中Host标签的autoDeploy属性。结构验证进入tomcat/webapps/demo/确认存在WEB-INF/目录及其子结构。日志定位重启Tomcat立即尾随日志tail -f logs/catalina.out。搜索你的应用名“demo”确认看到“启动成功”字样。如果看到“启动失败”根据堆栈错误如ClassNotFoundException, NoSuchMethodError解决依赖或版本冲突问题。3.2 第二步请求路径与映射分析假设我们的应用上下文是/demo有一个UserController。构造测试URLController:RestController RequestMapping(“/api”) public class UserController { GetMapping(“/user”) public String get() { return “ok”; } }正确的访问URLhttp://localhost:8080/demo/api/user使用工具测试不要只依赖浏览器。使用Postman或Curl进行测试可以更清晰地看到请求和响应。curl -v http://localhost:8080/demo/api/user-v参数会输出详细过程你可以看到服务器返回的完整HTTP状态码和头部信息确认确实是404。开启Spring MVC调试日志在application.properties中添加logging.level.org.springframework.web.servlet.DispatcherServletDEBUG logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingDEBUG重启应用后再次访问。日志会打印出所有已注册的控制器映射以及当前请求被匹配到了哪个映射或为何没被匹配。这是诊断Controller级别404的终极利器。3.3 第三步深入框架内部机制当基础路径和Controller映射都正确但依然404时需要怀疑“请求是否真的到达了DispatcherServlet”。检查DispatcherServlet映射在传统的基于web.xml的Spring MVC中DispatcherServlet通常被映射到/。但在Spring Boot中这是自动配置的。然而如果你有自定义的Servlet或Filter并且它们的映射路径是/*它们可能会“吃掉”所有请求导致请求无法到达Spring的DispatcherServlet。检查所有自定义的Servlet和Filter的映射范围。静态资源处理器尝试访问一个确定存在的静态资源比如http://localhost:8080/demo/css/style.css。如果也404说明静态资源路径配置有问题。在Spring Boot中可以通过spring.mvc.static-path-pattern来修改匹配模式通过spring.web.resources.static-locations来修改资源位置。Profile与条件化配置检查你的Controller或配置类是否被Profile注解标记而当前激活的Profile不匹配。或者是否使用了ConditionalOnProperty等条件注解导致某些配置在特定环境下未生效。4. 疑难杂症与进阶排查实录在实际开发中总会遇到一些不那么直观的404场景。这里记录几个让我印象深刻的案例。4.1 案例一IDEA热部署与上下文路径的“幽灵”现象在IntelliJ IDEA中使用Tomcat插件运行Spring MVC项目代码修改后热部署有时会出现404重启Tomcat又好了。排查IDEA的热部署机制有时会改变应用的上下文路径。例如它可能会将应用部署到一个带有时间戳或内部ID的路径下如/demo_war_exploded2。而你的浏览器可能还缓存着旧的访问地址/demo。或者IDEA的“Update resources”和“Update classes and resources”行为不同可能导致资源文件未同步。解决在IDEA的Tomcat运行配置中进入“Deployment”选项卡将你部署的Artifact的“Application context”固定为一个明确的值比如/。热部署后不要直接刷新旧标签页。关闭浏览器标签从IDEA控制台点击提供的链接重新打开或者手动拼接正确的URL。考虑使用Spring Boot DevTools它的热重启机制通常更可靠。4.2 案例二RequestMapping的继承陷阱现象一个基类Controller定义了RequestMapping(“/base”)子类继承后添加了RequestMapping(“/sub”)期望路径是/base/sub但访问却是404。排查在Spring MVC中RequestMapping注解在类上的映射路径是不会被继承的。子类上的注解会完全覆盖父类。因此子类的最终路径就是/sub而非/base/sub。解决如果需要在子类中拼接父类的路径需要在子类注解中显式写出完整路径RequestMapping(“/base/sub”)。或者重构设计避免使用继承的方式来组合路径而是采用组合或AOP等其他方式。4.3 案例三过滤器Filter中的请求转发/重定向错误现象在一个过滤器中对某些请求进行了request.getRequestDispatcher(“/some-page”).forward(request, response);操作但最终浏览器显示404。排查过滤器中的转发路径是服务器端路径。这个路径是相对于当前Servlet上下文的。如果你在过滤器中转发的路径/some-page没有对应的Servlet或控制器来处理那么请求链在转发后就会以404结束。更隐蔽的是如果转发后触发了另一个过滤器或拦截器它们可能会修改响应或中断请求。解决在过滤器中打印转发前后的路径和状态进行调试。确保转发目标路径是一个有效的、能处理的端点。仔细检查过滤器的doFilter链调用在转发或重定向之后通常不应该再调用chain.doFilter(request, response)否则会导致重复提交响应或产生不可预知的行为。4.4 常见问题速查表问题现象可能原因排查步骤访问localhost:8080即404Tomcat未启动或端口占用webapps/ROOT目录被删除检查进程、端口查看logs/catalina.out启动日志应用上下文路径访问404WAR包未解压/损坏WEB-INF缺失应用启动失败检查webapps下目录结构查看localhost.log应用部署日志静态资源css, js, img404资源文件不在默认目录静态资源路径被拦截检查文件物理位置检查WebMvcConfigurer和拦截器配置Controller接口404注解错误包未被扫描请求方法不匹配开启Spring MVC DEBUG日志检查ComponentScan使用Postman测试不同方法特定环境如生产/测试下404Profile特定配置未生效环境变量差异检查Profile注解对比不同环境的配置文件迁移至新环境如国产中间件后404规范兼容性问题依赖缺失配置方式不同查阅新中间件官方文档对比部署描述符检查启动类依赖5. 工具、习惯与预防措施良好的开发习惯和工具使用能从根本上减少404的出现。日志是第一位养成启动应用后第一时间查看日志的习惯。将日志级别调整为DEBUG或TRACE能获得海量信息。使用接口测试工具Postman, Insomnia 或 Swagger UI。它们能帮你精确构造HTTP请求方法、头、体排除浏览器缓存和自动行为的影响。单元测试与集成测试为你的Controller编写Spring MVC Test单元测试。这不仅能验证映射是否正确还能在代码变更后快速回归。SpringBootTest AutoConfigureMockMvc class UserControllerTest { Autowired private MockMvc mockMvc; Test void shouldReturnOk() throws Exception { mockMvc.perform(get(“/api/user”)) .andExpect(status().isOk()) .andExpect(content().string(“ok”)); } }清晰的文档与约定在团队内建立URL路径的命名规范并使用Swagger或类似的API文档工具自动生成并维护接口文档。让前端和测试同学能随时获取到准确的接口地址。理解“约定大于配置”深入理解你所用框架Spring Boot, Spring MVC的默认约定。知道静态资源在哪、欢迎页如何工作、错误页如何映射才能在自定义配置时做到心中有数避免冲突。解决Tomcat 404问题本质上是一个“缩小搜索范围”的过程。从最外层的网络访问到最内层的代码映射每一步排查都基于上一步的验证结果。这个过程没有银弹但有了这套系统性的方法和实战中积累的“坑点”地图你就能从被动地搜索零散答案转变为主动地、高效地定位问题根源。记住服务器永远不会说谎它给出的每一个404状态码在日志里都留下了通往真相的线索。