首页 / 资讯中心 / 文章详情

Spring Boot静态资源404:No mapping for GET根因排查与解决

Spring Boot静态资源404:No mapping for GET根因排查与解决 ★ FEATURED ARTICLE
不知道你是不是也撞过这个警告页面打开F12一片红前端同事已经开始催了后端控制台躺着一行No mapping for GET /static/js/app.js。很多开发第一次看到这个提示直接懵了静态资源明明放进项目了路径也核对过好几遍怎么就是加载不出来这个现象在SpringBoot 2.x里相当常见尤其是团队里有人接过老项目、改过WebMvc配置之后特别容易触发。它的本质是Spring MVC在处理这个URL时发现没有任何一个处理器Handler能接住这个请求。注意这不是程序异常而是DispatcherServlet输出的一条WARN日志但浏览器端拿到的结果就是404。这篇文章我会从现象、根因、解决办法到背后的映射机制把SpringBoot 2.x静态资源这条路完整走一遍也会把那些经常被混淆的“404到底是不是No mapping”讲清楚。不管你是刚接触SpringBoot的新人还是被这个问题反复折磨过的老开发看完应该能少走一整天弯路。1. 先搞清楚“No mapping for GET”到底是什么1.1 这条日志是怎么冒出来的先说结论这不是Spring Boot在“报错”而是DispatcherServlet在找不到任何可用的Handler时打印的一条日志。Spring Boot 2.x内部几乎所有的请求都会先经过DispatcherServlet它会拿着当前的URL去一堆HandlerMapping里挨个找对应的处理器。如果所有这些HandlerMapping都表示“这个URL我处理不了”就会走到兜底逻辑往响应里写一个404状态码同时在后台输出一行类似这样的日志WARN c.a....DispatcherServlet - No mapping for GET /static/js/app.jsSpring Boot 2.4之后对应的Spring Framework 5.3这条日志的级别改成了WARN所以控制台里非常显眼。更早的版本比如2.3以前的Spring 5.2.x这条日志其实是DEBUG级别很多人压根没注意过它。这也是为什么同一个问题有的人觉得“明明报错了”有的人觉得“好像也没什么动静”。1.2 它是日志不是异常别被WARN吓住很多新手看到“ERROR”“No mapping”就以为程序挂了其实程序活得好好的。你访问的URL如果没有对应的Controller、也没有对应的静态资源映射就会走到noHandlerFound这条路。默认情况下Spring Boot不会抛出NoHandlerFoundException而是直接返回404响应。这里有个容易迷惑的地方如果静态资源处理器注册了但文件本身不存在返回的也是404日志一般是“No resource found”之类并且不会出现“No mapping”。而“No mapping”这个日志说明连静态资源处理器都没匹配上这个URL。换句话说No mapping 请求的URL在Spring MVC里“无人认领”文件404 有人认领了但资源本身找不到这两个场景的原因和排法完全不同排查前先分清你撞的到底是哪一个。1.3 静态资源场景下的典型表现回到标题场景引入静态资源时报这个错最典型的表现有访问http://localhost:8080/static/js/app.js返回404访问http://localhost:8080/index.html返回404页面里引用的CSS、JS、图片全部加载不出来控制台刷出一堆No mapping for GET /xxx这些请求都没有经过你写的任何一个Controller方法它们期望由静态资源处理器ResourceHttpRequestHandler来响应。如果这个处理器没有被正确注册或者它注册了但文件不在指定位置就会出现上面的现象。2. 五种最常见的根因按出现频率排序2.1 约定大于配置Spring Boot只认这几个目录Spring Boot 2.x默认的静态资源目录是固定的四个按优先级从高到低排列classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/也就是说你放在src/main/resources/static/、src/main/resources/public/等目录下的文件才会被Spring Boot自动识别。这个设计本意是好的所有Web项目统一目录结构不用到处配置。但坑也在这里。很多人在src/main/java下面新建了一个static包放文件或者把资源随手丢在项目根目录Spring Boot根本不会去看那些地方于是404。更隐蔽的情况是目录层级错了。比如你在src/main/resources/static/下面放了一个index.html此时访问路径应该是http://localhost:8080/index.html。如果你非要在URL里加一层static变成http://localhost:8080/static/index.htmlSpring Boot会拿着这个路径去classpath:/static/static/index.html找自然找不到。2.2 自动配置被关了EnableWebMvc和继承WebMvcConfigurationSupport这一条才是“No mapping”日志最常见的代码来源也是很多人查半天查不出来的元凶。Spring Boot 2.x的静态资源映射本质上是靠WebMvcAutoConfiguration这个自动配置类注册的。它有一个生效条件ConditionalOnMissingBean(WebMvcConfigurationSupport.class)也就是说只要容器里出现了一个WebMvcConfigurationSupport类型的Bean整个WebMvc自动配置就全部失效。哪些操作会产生这个Bean在配置类上标注EnableWebMvc继承WebMvcConfigurationSupport很多老项目为了自定义拦截器、跨域配置习惯写这样的代码Configuration EnableWebMvc public class WebConfig implements WebMvcConfigurer { // 只写了拦截器相关配置 }一旦标了EnableWebMvcSpring Boot自动配置里的/**静态资源映射就失效了。而你的自定义配置类里又没有补上对应的ResourceHandler那么所有静态资源请求都会落到“No mapping”。继承了WebMvcConfigurationSupport也是同理。网上不少老教程喜欢用继承方式扩展MVC配置在Spring Boot 2.x里这是很危险的习惯因为继承本身就等于宣告“我不需要Spring Boot的Web自动配置”。2.3 自定义映射里没有兜底“/**”有些场景下开发者没有用EnableWebMvc只是实现了WebMvcConfigurer接口然后在addResourceHandlers里注册了几个自定义目录Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/assets/**) .addResourceLocations(classpath:/assets/); } }注意这种方式本身不会覆盖默认的/**映射但有一个前提默认映射必须还在。如果项目里同时存在其他因素导致WebMvcAutoConfiguration失效那你只注册了/assets/**访问/js/app.js、/css/style.css这些路径时就会No mapping。还有一种情况是访问路径和注册模式不匹配。比如你注册了/assets/**但页面HTML里写的却是/static/js/app.js这个URL一样匹配不上照样No mapping。页面代码里的引用路径、后端注册的Handler模式、文件的物理位置这三者必须对齐缺一环都不行。2.4 配置属性被误改add-mappings和static-path-patternSpring Boot 2.x里有几个和静态资源直接相关的配置项可能被无意中改掉spring: web: resources: add-mappings: false mvc: static-path-pattern: /resources/**spring.web.resources.add-mappings一旦设为false默认静态资源映射会整个关闭任何静态资源请求都会变成No mapping。这个配置项的设计初衷是让你彻底接管资源处理但绝大多数项目根本用不到。spring.mvc.static-path-pattern默认是/**如果你把它改成了/resources/**那只有/resources/开头的URL才会走静态资源处理器原来那些/static/、/js/、/css/开头的请求全部变成No mapping。这个坑在从网上复制配置时特别容易踩因为很多Spring MVC的旧示例会写上/resources/**。2.5 打包产物里根本没有这个文件最后一个根因比较低级但实战中真的会碰到代码里明明看到文件存在启动后就是404。问题可能出在编译和打包环节。Maven多模块项目里静态资源所在的模块没有配置resources或者maven-resources-plugin把src/main/resources里的内容过滤掉了IDE里改了文件但没有重新编译target/classes下面还是旧文件打成jar包后BOOT-INF/classes/static里压根没有对应的文件。这类问题在日志上的表现和“文件放错位置”类似但它不是代码逻辑错误而是构建流程问题。遇到这种情况直接去target/classes目录里翻一下看文件到底在不在比在代码里瞎猜高效得多。3. 解决方案从快速止血到彻底根治3.1 止血方案先把文件放到默认目录如果你是新手或者项目里暂时没有复杂的资源目录需求最简单的做法就是规规矩矩放文件。把静态资源放进src/main/resources/static/目录下然后访问路径去掉static前缀。比如src/main/resources/static/js/app.js对应的访问地址是http://localhost:8080/js/app.js如果你坚持要URL里带/static/前缀那就需要在目录里再套一层staticsrc/main/resources/static/static/js/app.js这不是什么优雅的写法但在紧急情况下确实能快速恢复页面。我个人不太推荐用这个方案作为长期方案因为它特别容易让后面的维护者产生路径困惑。3.2 常规方案用配置文件声明静态资源位置不想改目录结构或者需要同时支持多个资源目录、外部磁盘路径的直接改配置文件最省事。spring: web: resources: static-locations: classpath:/static/,classpath:/custom/,file:/data/upload/这个配置项会替换默认的四个目录改成你自己声明的列表。需要注意两点每个路径必须以/结尾classpath:/custom这种写法是错的启动时可能不报错但请求永远404如果同时需要外部路径file:前缀后面跟绝对路径配置之后/data/upload/目录下如果有一张a.jpg访问http://localhost:8080/a.jpg就能直接拿到。3.3 进阶方案代码注册ResourceHandler支持文件系统路径有些场景需要在代码里动态判断路径或者临时加一个映射这时用ResourceHandlerRegistry最灵活Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 追加自定义映射不覆盖默认映射 registry.addResourceHandler(/assets/**) .addResourceLocations(classpath:/assets/) .setCachePeriod(3600); // 映射外部磁盘目录 registry.addResourceHandler(/upload/**) .addResourceLocations(file:/data/upload/); } }这个配置的关键点是addResourceHandler里的/assets/**表示URL匹配模式**代表任意层级addResourceLocations里的classpath:/assets/必须以/结尾file:/data/upload/映射的是服务器磁盘路径适合上传文件预览的场景setCachePeriod是设置HTTP缓存时长单位秒页面调试时建议设小一点如果只是追加映射没有EnableWebMvc、没有继承WebMvcConfigurationSupport默认的/**映射依然有效项目里原本的/js/、/css/路径不会受影响。3.4 避坑方案检查并移除多余的EnableWebMvc这是解决“No mapping”日志最直接的动作。翻遍项目代码找到所有标注了EnableWebMvc的配置类如果没有充分理由直接把这个注解删掉。同样的把配置类改成实现WebMvcConfigurer接口而不是继承WebMvcConfigurationSupport。Configuration public class WebConfig implements WebMvcConfigurer { // 原来的拦截器、跨域等配置保留在这里 }改完之后重启项目静态资源基本就回来了。这么做不会丢失你已有的自定义配置因为WebMvcConfigurer本身就是用来扩展MVC的Spring Boot会自动收集容器里所有WebMvcConfigurer实现类。3.5 过渡方案用Controller forward转发静态页面如果你正在处理一个紧急故障来不及改配置又必须让某个页面先能访问可以用一个临时的Controller做转发Controller public class PageController { GetMapping(/admin) public String admin() { return forward:/admin/index.html; } }这个方案只适合过渡本质是绕过静态资源映射问题让请求走进Controller体系。一旦涉及大量静态资源不可能为每个文件都写一个Mapping方法所以最终还是要回到前面几个方案上。4. 深挖原理Spring Boot 2.x静态资源映射是怎么工作的4.1 一次静态资源请求的完整旅程当你浏览器里输入http://localhost:8080/js/app.js实际发生的事比想象中复杂请求到达Spring Boot内嵌的TomcatTomcat把请求转交给DispatcherServletDispatcherServlet按顺序遍历所有HandlerMapping尝试找一个能处理这个URL的Handler前几个HandlerMapping比如处理RequestMapping的RequestMappingHandlerMapping对这个URL不感兴趣轮到静态资源的映射时匹配上/**模式返回对应的ResourceHttpRequestHandlerDispatcherServlet调用这个HandlerHandler再根据配置的资源位置去classpath或文件系统找文件第5步就是关键。如果某一步HandlerMapping匹配不上最终就会走到noHandlerFound打出“No mapping”日志。4.2 WebMvcAutoConfiguration做了什么Spring Boot 2.x的静态资源映射核心代码在WebMvcAutoConfiguration里。它内部有一个默认的WebMvcConfigurer实现类在addResourceHandlers方法里注册了两组资源映射// 第一组webjars registry.addResourceHandler(/webjars/**) .addResourceLocations(classpath:/META-INF/resources/webjars/); // 第二组默认静态资源路径 /**对应四个默认目录 registry.addResourceHandler(/**) .addResourceLocations(getResourceLocations(...));/**这个模式是全路径匹配理论上能接住所有没被Controller处理的请求。它背后的ResourceHttpRequestHandler会按照你配置的static-locations列表挨个目录去找文件。所以默认情况下Spring Boot项目并不容易出现“No mapping”因为/**把底兜住了。真正让它失效的就是我在2.2节说的那些操作——EnableWebMvc关掉自动配置或者add-mappings被置为false。4.3 为什么自定义配置容易“弄丢”默认映射这里有个细节很多教程没讲清楚单纯实现WebMvcConfigurer接口并重写addResourceHandlers方法并不会弄丢默认映射。原因在于Spring Boot在自动配置里已经有一个WebMvcConfigurer实现类在注册/**映射了而你自定义的WebMvcConfigurer只是往同一个ResourceHandlerRegistry里追加注册。除非你在addResourceHandlers里也注册一个/**否则默认的那个依然存在。真正会让默认映射丢失的是EnableWebMvc或者继承WebMvcConfigurationSupport。这两种方式会让整个WebMvcAutoConfiguration失效自动配置里的那个WebMvcConfigurer根本不会出现在容器里自然也就没人帮你注册/**了。这也解释了为什么很多老项目一升级Spring Boot版本就出问题老代码里用EnableWebMvc的人太多了以前依赖的“乱写也能跑”的宽松环境在版本升级后突然不灵了。4.4 版本差异Spring Boot 2.6之后的路径匹配变化还有一个版本相关的坑值得提一下。Spring Boot 2.6之后Spring MVC默认把路径匹配策略从AntPathMatcher换成了PathPatternParser。两者在大部分场景下表现一致但有个别通配符行为有差异尤其是嵌套路径的匹配。如果你从2.5以下升级到2.6以上发现某种路径规则突然失效了可以尝试加配置切回旧的匹配策略spring: mvc: pathmatch: matching-strategy: ant_path_matcher不过静态资源的/**匹配在这种切换中基本不受影响这个配置项更多影响的是Controller和拦截器里的复杂路径规则。提出来只是让你知道有这回事排查问题时少走弯路。5. 排查实操一套3分钟定位的口诀5.1 真实排查步骤遇到静态资源404别急着改代码按下面这套流程走第一步看日志。确认是不是真的有No mapping for GET。没有这行日志说明请求已经被某个Handler接住了问题在文件本身直接去检查目录和打包产物。第二步看配置。全局搜索EnableWebMvc和WebMvcConfigurationSupport搜到就重点排查是不是它们导致的。再看application.yml里有没有设置add-mappings: false以及static-path-pattern的当前值。第三步看目录。进入target/classes目录确认静态文件是否真的被编译进去了。多模块项目还要检查资源插件有没有排除掉某些后缀。第四步看路径。对照文件物理位置、URL里的前缀、addResourceHandler注册的模式三者是否对齐。用手动curl测一下curl -I http://localhost:8080/js/app.js看返回的状态码和响应头比浏览器F12更直观。5.2 常见场景速查表场景日志表现核心原因优先处理方式文件在static/js下URL写成/static/js无No mapping浏览器404URL前缀多了一层static去掉URL里的static前缀配置类有EnableWebMvcDispatcherServlet打出No mapping自动配置失效删除EnableWebMvc配置类继承WebMvcConfigurationSupportDispatcherServlet打出No mapping自动配置失效改为实现WebMvcConfigurer自定义Handler只注册了/assets/**访问/js/时No mapping访问与注册模式不匹配补上/**或修正URL配置文件里add-mappings: false所有静态资源No mapping默认映射被关闭删除该配置或置true多模块打包后没有静态文件无No mapping浏览器404构建/打包问题检查target目录和resources配置6. 避坑经验总结那些文档里没写但一定要知道的事6.1 路径结尾的斜杠是个大坑addResourceLocations(classpath:/static)和addResourceLocations(classpath:/static/)看着差不多实际行为天差地别。前者不会在目录末尾补上分隔符拼接出来的资源路径大概率是错的。这个习惯我从早期踩过一次后就记住了凡是资源位置末尾一律带/。YAML里的static-locations也是同理多个目录之间用逗号分隔每个目录后面都要跟/spring: web: resources: static-locations: classpath:/static/,classpath:/public/,file:/data/upload/6.2 多个目录是有优先级的配置多个静态资源位置后Spring Boot会按声明顺序依次查找。两个目录里存在同名文件时排在前面的目录生效。这个特性可以拿来设计“默认资源用户上传覆盖”的架构默认静态资源目录放在前面用户上传目录放在后面同名文件上传后就能覆盖默认展示。但如果顺序搞反了上传的文件永远不会被展示出来因为前面的目录里已经有一个同名文件了。6.3 调试阶段缓存会让你怀疑人生Spring Boot默认对静态资源会带上缓存响应头。文件改完刷新页面还是旧的第一反应往往是“配置没生效”其实是浏览器本地缓存。调试阶段建议要么在配置里动态调整setCachePeriod(0)要么用无痕窗口测试。部署到生产环境再把缓存时长调高这样前端性能更好后端也能少扛一些请求压力。6.4 最后说一个我个人的习惯接手任何Spring Boot项目我第一件事就是全局搜EnableWebMvc和WebMvcConfigurationSupport这两个关键词。只要搜到静态资源的基本盘就有风险。新代码一律implements WebMvcConfigurer不标EnableWebMvc配置项尽量走YAML自定义资源映射只追加、不覆盖。这套习惯帮我少踩了无数坑也希望对你排查“No mapping for GET”有用。
阅读完成 · 觉得有帮助?
咨询建站