Spring Boot 从“会用”到“精通”:静态资源原理

静态资源原理

一、是什么 ------ 静态资源访问规则是什么?

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
}

⚠️ 关键结论 :欢迎页生效有严格的双重条件

  1. 静态资源目录下有 index.html
  2. staticPathPattern 必须是 "/**"(默认值)

**一旦你在配置文件中设置了 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 怎么办?

排查步骤

  1. 确认文件放在了 staticpublic 目录下(不是 templates!)
  2. 确认没有自定义 spring.resources.static-locations 覆盖默认路径
  3. 确认 spring.web.resources.add-mappings 没有被设为 false
  4. 如果配置了 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
相关推荐
奋斗的袍子0071 小时前
springboot集成国密算法SM2
java·spring boot·算法
caibixyy1 小时前
Springboot + flowable6.8.0
spring boot·flowable6.8.0
JAVA面经实录9171 小时前
SpringBoot 全套完整版学习文档(零基础+实战+面试+源码)
java·spring boot·spring·架构
接着奏乐接着舞1 小时前
springcloud xxl-job
后端·spring·spring cloud
jasnet_u1 小时前
SpringCloud中Feign透传traceId及日志切面配置
java·spring cloud·feign·日志系统
我是一颗柠檬1 小时前
【Redis】Cluster集群Day11(2026年)
数据库·redis·后端·缓存
nvd111 小时前
从 Spring 到 Quarkus:为什么依赖注入正在从“运行时”退回“编译期”?
java·后端·spring
JAVA面经实录9171 小时前
SpringCloud 完整体系学习文档
java·spring cloud
爱吃羊的老虎1 小时前
【JAVA】Java微服务—Spring Cloud 里用来做服务调用的工具OpenFeign
java·微服务·开源