静态资源原理
一、是什么 ------ 静态资源访问规则是什么?
1. 什么是"静态资源"?
静态资源就是不需要服务端动态处理、直接返回给浏览器的文件:JS、CSS、图片、HTML 页面、字体文件等。
2. Spring Boot 的默认规则
Spring Boot 为静态资源做了约定大于配置 的设计。默认情况下,只要把静态资源放到以下四个类路径目录下,就能直接通过 URL 访问:
| 类路径目录 | 说明 |
|---|---|
/static |
最常见,放 JS/CSS/images |
/public |
公共资源 |
/resources |
资源文件(注意:不是类路径根目录的 resources) |
/META-INF/resources |
元信息中的资源 |
优先级 :
/META-INF/resources>/resources>/static>/public
当同名文件存在于多个目录时,排在前面的优先返回。
项目中的实际目录结构 (boot-05-web-01 模块):
src/main/resources/
├── static/
│ ├── favicon.ico
│ ├── index.html
│ └── zhifubao.png
├── public/
│ ├── aaaaa.jpg
│ └── timg.gif
├── resources/
│ └── timg.jpg
├── haha/ ← 自定义静态资源路径
│ ├── index.html
│ └── zhifubao.png
└── templates/
└── success.html ← 模板页面,不能直接访问!
3. 关键区分:static 目录 vs templates 目录
| 目录 | 能否直接访问 | 适用场景 |
|---|---|---|
static/ |
✅ 能 | 纯静态文件:JS、CSS、图片、HTML |
public/ |
✅ 能 | 同上 |
resources/ |
✅ 能 | 同上 |
templates/ |
❌ 不能直接访问 | 模板页面(Thymeleaf),必须通过 Controller 跳转 |
二、为什么 ------ 为什么要这样设计?
1. 简化 Web 开发
在传统 Spring MVC 中,静态资源的放行通常需要在 XML 中配置 <mvc:resources> 或重写 WebMvcConfigurer.addResourceHandlers(),稍有不慎就会导致静态资源 404。
Spring Boot 直接帮我们配好了:你只管往 static 里放,URL 直接就能访问。
2. 欢迎页的智能处理
当用户访问 http://localhost:8080/ 时,Spring Boot 会自动寻找 index.html 作为欢迎页。这个寻址逻辑也是通过自动配置 + WelcomePageHandlerMapping 实现的。
3. 可配置的灵活性
虽然约定好了四个默认目录,但 Spring Boot 允许你完全自定义:
- 想改静态资源路径前缀?配
spring.mvc.static-path-pattern - 想把静态资源放别的地方?配
spring.resources.static-locations - 不想启用静态资源?
spring.web.resources.add-mappings=false
三、怎么做 ------ 静态资源的源码机制
1. 静态资源的自动配置
Spring Boot 对静态资源的配置,都在 WebMvcAutoConfiguration 类中。这个自动配置类生效需要满足以下条件:
java
@AutoConfiguration(after = {DispatcherServletAutoConfiguration.class, ...})
@ConditionalOnWebApplication(type = Type.SERVLET) // 必须是 Servlet Web 应用
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) // 用户没有全面接管 MVC
public class WebMvcAutoConfiguration {}
其核心内部类 WebMvcAutoConfigurationAdapter 有一个有参构造器,Spring 会自动从容器中按类型注入所有依赖:
java
@Configuration(proxyBeanMethods = false)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
// ★ 有参构造器 ------ 所有参数从容器中按类型自动注入
public WebMvcAutoConfigurationAdapter(
WebProperties webProperties, // 绑定 spring.web.* 配置
WebMvcProperties mvcProperties, // 绑定 spring.mvc.* 配置
ListableBeanFactory beanFactory, // Spring Bean 工厂
ObjectProvider<HttpMessageConverters> messageConvertersProvider, // 消息转换器
ObjectProvider<ResourceHandlerRegistrationCustomizer> customizer, // 资源处理器定制器
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = webProperties.getResources(); // ← 静态资源相关配置从这里来
this.mvcProperties = mvcProperties; // ← spring.mvc.* 配置从这里来
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
// ...
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
return; // spring.web.resources.add-mappings=false 可禁用所有静态资源映射
}
// 1. WebJars 的映射:/webjars/** → classpath:/META-INF/resources/webjars/
addResourceHandler(registry, "/webjars/**",
"classpath:/META-INF/resources/webjars/");
// 2. 静态资源的映射:/** → 四个默认目录
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
this.resourceProperties.getStaticLocations());
}
}
默认值来自哪里?
staticPathPattern 来自 WebMvcProperties,默认值 /**:
java
// WebMvcProperties
private String staticPathPattern = "/**";
staticLocations 来自 WebProperties.Resources 内部类,硬编码了四个默认路径:
java
// WebProperties.Resources
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
new String[]{"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"};
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}
关键理解 :
staticLocations的默认值是数组 ,包含了四个目录。addResourceHandler方法内部会遍历registry.hasMappingForPattern(pattern)判断是否已存在映射------如果你通过配置文件自定义了static-path-pattern,Spring 就用你的;否则用默认的/**。
默认配置解析:
staticPathPattern默认值 =/**(所有路径都可能映射到静态资源)staticLocations默认值 =["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"]
2. 静态资源访问的完整流程
浏览器请求 http://localhost:8080/zhifubao.png
↓
DispatcherServlet.doDispatch()
↓
getHandler() 遍历 HandlerMapping
↓
RequestMappingHandlerMapping 没匹配到(没有对应的 @RequestMapping)
↓
SimpleUrlHandlerMapping(静态资源处理器映射)匹配到了!
↓
返回 ResourceHttpRequestHandler → 读取文件 → 写出响应
为什么 RequestMappingHandlerMapping 没匹配到会继续往下找?
因为 getHandler() 是遍历所有 HandlerMapping 的:
java
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler; // 任何一个匹配就返回
}
}
}
return null;
}
请求先经过 RequestMappingHandlerMapping(处理 @RequestMapping 注解),如果没匹配到,继续尝试 SimpleUrlHandlerMapping(处理静态资源),这就是静态资源能被正确访问的底层机制。
3. 欢迎页原理 ------ WelcomePageHandlerMapping
在 Spring Boot 的 WebMvcAutoConfiguration 中,还有一个专门的欢迎页映射器:
java
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ApplicationContext applicationContext,
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping mapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext),
applicationContext,
getWelcomePage(), // ← 第三个参数:index.html 资源
this.mvcProperties.getStaticPathPattern() // ← 第四个参数:staticPathPattern
);
return mapping;
}
WelcomePageHandlerMapping 构造器的关键逻辑:
java
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext,
Resource welcomePage, // index.html 资源对象
String staticPathPattern) { // 我们配置的静态资源路径前缀
// ★ 条件一:welcomePage 不为空(static 目录下有 index.html)
// ★ 条件二:staticPathPattern 必须是 "/**"
if (welcomePage != null && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html"); // → 转发到 index.html
}
// 如果不符合条件(比如改过 static-path-pattern),尝试找模板中的 index 页面
else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
this.setRootViewName("index"); // → 找 Controller 中能处理 /index 请求的方法
}
// 都没有 → 欢迎页不生效 → 访问 / 返回 404
}
⚠️ 关键结论 :欢迎页生效有严格的双重条件:
- 静态资源目录下有
index.htmlstaticPathPattern必须是"/**"(默认值)**一旦你在配置文件中设置了
spring.mvc.static-path-pattern=/res/**,欢迎页立即失效!**因为条件"/**".equals(staticPathPattern)不再成立。同样的,
favicon.ico也是浏览器主动请求/favicon.ico,当你修改了静态资源路径前缀,浏览器请求的/favicon.ico就无法匹配到/res/favicon.ico,所以 favicon 也会一起失效。
4. 自定义静态资源配置(项目实例)
在 boot-05-web-01 项目中,配置文件展示了如何自定义静态资源:
yaml
# 源码位置:springboot2-master/boot-05-web-01/src/main/resources/application.yaml
spring:
resources:
static-locations: [classpath:/haha/] # 把静态资源路径改成了 /haha/
add-mappings: true # 是否启用静态资源映射(默认 true)
cache:
period: 11000 # 浏览器缓存时间(秒)
这样配置后,原来的 /static、/public 等默认目录全部失效,只有 /haha/ 目录下的文件才能被访问。
还可以修改 URL 前缀:
yaml
spring:
mvc:
static-path-pattern: /res/** # 访问路径必须加 /res/ 前缀
配置后的效果:原来 http://localhost:8080/zhifubao.png 变成了 http://localhost:8080/res/zhifubao.png。
5. addResourceHandler 内部细节:不仅仅是路径映射
每次 addResourceHandler 调用最终都会走到这个方法:
java
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
Consumer<ResourceHandlerRegistration> customizer) {
if (!registry.hasMappingForPattern(pattern)) { // 避免重复注册
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration); // 设置资源路径
// ★ 设置缓存策略
registration.setCachePeriod(
this.getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(
this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(
this.resourceProperties.getCache().isUseLastModified());
}
}
缓存的过期时间单位是秒 ,在 WebProperties.Cache 中定义:
java
public static class Cache {
@DurationUnit(ChronoUnit.SECONDS)
private Duration period; // 可通过 spring.web.resources.cache.period 配置
}
这就是为什么你在项目中配置 spring.resources.cache.period=11000 能生效的原因。
6. WebJars ------ 前端库的 Maven 依赖管理
前端库(jQuery、Bootstrap 等)也可以像 Java 依赖一样通过 Maven 引入:
xml
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
引入后,访问路径为:http://localhost:8080/webjars/jquery/3.5.1/jquery.js。
这是 Spring Boot 的 WebJarsResourceResolver 在起作用。
四、常见问题与避坑指南
Q1:静态资源 404 怎么办?
排查步骤:
- 确认文件放在了
static或public目录下(不是templates!) - 确认没有自定义
spring.resources.static-locations覆盖默认路径 - 确认
spring.web.resources.add-mappings没有被设为false - 如果配置了
server.servlet.context-path,URL 需要加此前缀
Q2:templates 目录下的 HTML 为什么访问不了?
templates 下的页面不能通过 URL 直接访问,必须通过 Controller 跳转:
java
// 源码位置:springboot2-master/boot-05-web-01/.../controller/ViewTestController.java
@Controller
public class ViewTestController {
@GetMapping("/atguigu")
public String atguigu(Model model){
model.addAttribute("msg", "你好 guigu");
return "success"; // → 转发到 templates/success.html
}
}
这是 Spring Boot 的安全设计:模板页面可能包含服务端变量(Thymeleaf 的 ${...}),直接暴露给用户访问会有安全隐患。
Q3:静态资源和动态请求路径冲突了怎么办?
如果 static-path-pattern 是 /**(默认),而你刚好有一个 Controller 的路径和静态文件重名,Controller 优先级更高 (因为 RequestMappingHandlerMapping 排在 SimpleUrlHandlerMapping 前面)。
Q4:为什么我改了 static-path-pattern 后,欢迎页和 favicon 都失效了?
这是同一个根因。欢迎页生效的条件是 "/**".equals(staticPathPattern)------源码中的精确字符串比较 。一旦你把 static-path-pattern 改成 /res/**,这个 equals 就返回 false,欢迎页直接跳过。
favicon.ico 同理:浏览器始终请求的是 /favicon.ico,而不是 /res/favicon.ico。改了前缀后,浏览器请求的路径匹配不到你的静态资源规则,自然 404。
解决方案 :如果确实需要自定义前缀,就别依赖 Spring Boot 的欢迎页机制,自己写一个 Controller 处理 / 请求。
五、总结
| 核心点 | 说明 |
|---|---|
| 默认路径 | /static、/public、/resources、/META-INF/resources |
| 访问规则 | 放在上述目录的文件可直接通过 URL(无前缀)访问 |
| 优先级 | RequestMapping > 静态资源 Mapping |
| 欢迎页 | index.html 自动作为 / 的响应 |
| 可配置 | spring.resources.static-locations 自定义路径 |
| 模板隔离 | templates 目录不能直接访问,必须通过 Controller |