Spring Boot 的 Web 模块在自动装配阶段就内置了静态资源处理逻辑,如果不加干预,放在 classpath:/static、classpath:/public、classpath:/resources 或 classpath:/META-INF/resources 下的文件都能被直接访问。但默认规则并不是简单的全盘扫描,它有自己的优先级和路径匹配方式。自定义映射时一旦忽略这些细节,可能出现文件访问不到、首页被覆盖或者缓存策略失效的情况。本文按照默认映射、自定义映射、缓存控制、生产策略四个部分展开,帮助把静态资源管理做得更清晰。

默认静态资源映射的执行顺序
Spring Boot 自动配置类 WebMvcAutoConfiguration 在容器启动时会注册两个静态资源处理器:一个是 /webjars/**,对应 classpath:/META-INF/resources/webjars/;另一个是 /**,它会按固定顺序查找 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/ 四个位置。这个顺序决定了同名文件谁能胜出,例如 classpath:/static/app.css 和 classpath:/public/app.css 同时存在时,访问 /app.css 会返回 classpath:/static 下的版本,因为它的查找优先级更高。
如果项目结构比较传统,可能只有 src/main/resources/static 一个目录。此时不要误以为 /static/ 是 URL 前缀,实际上默认访问路径就是根路径,例如 static/css/main.css 会被映射为 /css/main.css。如果希望 URL 中保留 /static/ 前缀,可以通过 spring.mvc.static-path-pattern 配置。下面这段配置将 URL 前缀改为 /resources/**,并把扫描位置限制到两个目录:
spring:
mvc:
static-path-pattern: /resources/**
web:
resources:
static-locations:
- classpath:/static/
- classpath:/custom/
这里需要特别注意:自定义 static-locations 后,默认的四个目录不会继续保留,而是完全被覆盖。如果原本 classpath:/public 下还有文件,配置之后就会访问不到。因此推荐在保留 classpath:/static 的基础上追加目录,而不是只写一个新的路径。默认顺序和优先级适合大多数小型项目,但在路径前缀或外置文件场景中,仅靠配置文件还不够,需要注册自定义的 ResourceHandler。
通过 WebMvcConfigurer 自定义静态资源映射
当静态文件不在标准 classpath 目录,而是位于磁盘的某个文件夹,或者需要把 /files/** 映射到外置上传目录时,可以实现 WebMvcConfigurer 并重写 addResourceHandlers。这样既能使用文件系统路径,也能单独设置浏览器缓存头。下面是一个典型配置,假设上传目录在 Windows 下为 C:\springboot\upload\,通过 URL 前缀 /files/** 访问:
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.Duration;
@Configuration
public class StaticResourceConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**")
.addResourceLocations("file:///C:/springboot/upload/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(30)).cachePublic());
}
}
代码中的 file:///C:/springboot/upload/ 是 URL 写法,实际目录就是 C:\springboot\upload\。如果部署在 Linux 服务器,可以写成 file:/home/app/upload/,结尾必须保留斜杠,否则路径拼接可能不完整。配置完成后,/files/avatar.png 会直接读取对应磁盘文件。还要注意,addResourceHandler 注册的映射会追加到默认映射之后,如果 URL 冲突,执行顺序取决于注册顺序,有时会出现默认 /** 先匹配的情况,因此自定义前缀最好与现有映射区分开。
除了外置文件,这个方式还适合处理不同版本的静态资源。例如给 /assets/** 单独设置一年的缓存,而不影响 /index.html。可以通过多次调用 registry.addResourceHandler 实现不同路径不同策略。这样比全局配置更灵活,也更容易在代码里加了注释说明原因。
在自定义映射时,如果还希望支持 gzip 压缩或版本化资源解析,可以继续使用 ResourceChainRegistration。不过默认情况下,Spring Boot 已经为静态资源提供了缓存控制能力,下一节会从配置和代码两个角度说明如何让缓存头值符合预期。
静态资源缓存头的设置与优先级
浏览器缓存静态资源主要依赖响应头 Cache-Control。它告诉浏览器是否缓存、缓存多久、是否允许使用过期副本。Spring Boot 提供了两组配置:spring.web.resources.cache.period 和 spring.web.resources.cache.cachecontrol。前者是比较旧的简化配置,可以写 3600 表示 3600 秒;后者支持更完整的表达,例如 max-age、no-cache、no-store、must-revalidate。如果两个都配置,cachecontrol 会优先生效,因此实际项目建议只用 cachecontrol,避免混淆。
spring:
web:
resources:
cache:
cachecontrol:
max-age: 30d
cache-public: true
# 避免同时使用 period,下面这行仅作示意
# period: 3600
上面的配置让所有默认静态资源在浏览器端缓存 30 天,并标记为可公开缓存。单纯从减少请求数看效果很好,但一旦 CSS 或 JS 文件内容更新,用户在 30 天内可能仍然读取旧文件。为了让更新及时生效,常见的做法是给文件名加上内容哈希,例如 app-3f9a2c.js。当文件内容变化时哈希值改变,URL 也随之变化,浏览器会把它当作新资源重新请求。
如果更倾向于由代码精确控制,也可以在 addResourceHandlers 中调用 setCacheControl。例如对图片目录设置 CacheControl.maxAge(Duration.ofDays(90)).cachePublic(),对 HTML 文件设置 CacheControl.noCache()。这种方式能让后端开发人员在 Java 代码中看到缓存策略,不需要翻看配置文件。无论哪种设置,最终目的都是让不常变化的资源长缓存、需要及时更新的入口文件短缓存或不缓存。
生产环境中的缓存更新与版本化处理
生产环境最怕出现用户访问到旧版页面、旧版脚本的情况。单纯把 max-age 调小虽然能缓解,但会增加请求数量和带宽消耗。正确思路是让 HTML 文件保持较短的缓存或不缓存,而脚本、样式和图片使用长缓存加内容版本。Spring Boot 支持通过 ResourceChainRegistration 为静态资源添加内容哈希解析,配置示例如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.ContentVersionStrategy;
import org.springframework.web.servlet.resource.VersionResourceResolver;
@Configuration
public class VersionedResourceConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/")
.resourceChain(true)
.addResolver(new VersionResourceResolver()
.addContentVersionStrategy("/**"));
}
}
这个配置会为 /assets/** 下的资源解析带内容版本号的请求,例如 /assets/app-3f9a2c.css 会被解析到 classpath:/static/assets/app.css。不过它不会自动修改页面中的引用路径,实际开发还需要配合模板引擎或构建工具生成带哈希的 URL。Spring Boot 的 ResourceUrlEncodingFilter 能够帮助在 Thymeleaf、FreeMarker 等模板中转换地址,其原理依赖于资源链和版本策略。
另一种更简单的落地方法是借助前端构建工具生成版本号,例如 Webpack、Vite 都支持在产物文件名中加入 hash。后端只需要把 /assets/** 设置为一年甚至更久的缓存,并保证 index.html 使用 no-cache。这样每次发布只更新 HTML 文件,浏览器会重新获取 HTML,然后加载新的哈希资源。对一个 Spring Boot 单体应用来说,该策略可以同时兼顾缓存命中率和更新及时性。
最后还要留意 CDN 或反向代理层的缓存规则。即使响应头设置了 Cache-Control,如果 Nginx 或 CDN 覆盖了头信息,最终效果仍可能不符合预期。排查缓存问题时,优先用浏览器开发者工具查看实际响应头,确认是后端配置未生效还是中间层改写了缓存策略。静态资源管理并不复杂,但把默认规则、自定义映射和缓存头三件事串起来后,排查效率会高很多。
Spring Boot静态资源映射缓存控制修改时间:2026-10-04 18:58:08