一、简述

问题

web开发有很多静态资源,比如html、图片、css等。
以前的传统的spring项目(比如ssm框架的项目)里面有一个webapp目录,只要把静态资源放到该目录下就可以直接访问,但是SpringBoot工程没有这个目录,应该如何处理?

解决

Spring Boot 默认为我们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各种属性

二、读源码

之前在我们ssm项目用tomcat启动项目时,会默认加载很多配置文件,控制台能看到在读很多文件。
同理,springboot启动时,会默认加载很多xxxAutoConfiguration类(自动配置类)。
其中一个自动配置类:WebMvcAutoConfiguration,集中了大部分springMVC的功能

基于 springboot2.x

WebMvcAutoConfiguration

1.1如何找WebMvcAutoConfiguration

image.png
首先找到springboot启动类
image.png
@EnableAutoConfiguration
image.png
image.png
上一步点进来后可以看到很多自动配置类
image.png
往下滑,可以看到WebMvcAutoConfiguration
image.png
最终定位:
image.png

1.2 注册到容器中(ConditionalOnXXX)

WebMvcAutoConfiguration满足以下ConditionalOnxxx条件,类是生效的,并把其对象注册到容器中。
image.png

1.3 内部静态类 WebMvcAutoConfigurationAdapter

这是一个配置类,配置文件的属性和xxx进行了绑定

1.3.1 注解@EnableConfigurationProperties

WebMvcAutoConfigurationAdapter上方进行了注解@EnableConfigurationProperties
这个注解的作用是:使使用 @ConfigurationProperties 注解的类生效

@EnableConfigurationProperties,包含了三个类WebMvcProperties,ResourceProperties,WebProperties ,点进去会发现这三个类都用 @ConfigurationProperties 进行了注解

因此,WebMvcAutoConfigurationAdapter作用之一,就是使这三个类中进行声明了前缀的配置文件都生效
image.png
来看下三个类
1)WebMvcProperties
image.png
2)ResourceProperties
image.png
3)WebProperties
image.png
WebMvcProperties它是与配置文件前缀spring.mvc相关联的。
ResourceProperties它是与配置文件前缀spring.resources相关联。
WebProperties它是与配置文件前缀spring.web相关联。

1.3.2 构造器

构造器初始化后,我们可以从容器中拿到 配置文件的内容
主要是以下三个参数,构造器初始化后,可以拿到这三组配置文件的内容
ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties
其他参数如要深究可以自行研究,这里没有展开了解
image.png

1.3.3 实现接口

WebMvcAutoConfigurationAdapter 是个适配器,实现了 WebMvcConfigurer 和 ServletContextAware 接口方法
image.png
WebMvcConfigurer 接口:
image.png
先来看下 WebMvcConfigurer 常用方法

  1. /** 拦截器配置 */
  2. void addInterceptors(InterceptorRegistry var1);
  3. /** 视图跳转控制器 */
  4. void addViewControllers(ViewControllerRegistry registry);
  5. /**静态资源处理 */
  6. void addResourceHandlers(ResourceHandlerRegistry registry);
  7. /** 默认静态资源处理器 */
  8. void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
  9. /** 这里配置视图解析器 */
  10. void configureViewResolvers(ViewResolverRegistry registry);
  11. /** 配置内容裁决的一些选项*/
  12. void configureContentNegotiation(ContentNegotiationConfigurer configurer);
  13. /** 解决跨域问题 **/
  14. public void addCorsMappings(CorsRegistry registry) ;

1.3.4 方法 addResourceHandlers

WebMvcAutoConfigurationAdapter 重写实现了方法 addResourceHandlers,这个方法是 静态资源进行处理的。

  1. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  2. //this.resourceProperties.isAddMappings()如果对resourceProperties进行配置,那么就会禁用默认资源处理
  3. //resourceProperties.isAddMappings() 默认为true
  4. if (!this.resourceProperties.isAddMappings()) {
  5. logger.debug("Default resource handling disabled");
  6. } else {
  7. this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
  8. this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
  9. registration.addResourceLocations(this.resourceProperties.getStaticLocations());
  10. if (this.servletContext != null) {
  11. ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
  12. registration.addResourceLocations(new Resource[]{resource});
  13. }
  14. });
  15. }
  16. }

1)先看 if 语句块

  1. if (!this.resourceProperties.isAddMappings()) {
  2. logger.debug("Default resource handling disabled");
  3. }

resourceProperties.isAddMappings() 默认为true
如果isAddMappings为false,就打印默认资源映射路径失效了。
isAddMappings方法其实就是返回一个addMappings变量(在WebProperties中)
addMappings的含义就是运行访问静态资源,如果你设置成false,就是禁用所有静态资源映射
2)再看ELSE 的webjars路径

  1. this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");

结论:
所有 /webjars/ ,都去 classpath:/META-INF/resources/webjars/ 找资源;**

webjars:以jar包的方式引入静态资源;
网址:https://www.webjars.org/
在webjars的官网里面找出对应资源的pom依赖,添加到pom文件中

  1. <!-- jquery的webjar-->
  2. <dependency>
  3. <groupId>org.webjars</groupId>
  4. <artifactId>jquery</artifactId>
  5. <version>3.3.1</version>
  6. </dependency>

举例:http://localhost:8888/webjars/jquery/3.3.1/jquery.js

3)再看 ELSE 的 mvcProperties 和 resourceProperties 文件路径

  1. this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
  2. registration.addResourceLocations(this.resourceProperties.getStaticLocations());
  3. if (this.servletContext != null) {
  4. ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
  5. registration.addResourceLocations(new Resource[]{resource});
  6. }
  7. });

结论:
所有 / 都去默认四个地址下找资源
四个地址:
classpath:/META-INF/resources/,
classpath:/resources/,
classpath:/static/,
classpath:/public/**

mvcProperties
this.mvcProperties.getStaticPathPattern()
mvcProperties是WebMvcProperties类的对象,查看WebMvcProperties 属性staticPathPattern的值是 /**
因此,我们访问静态资源的方式,是根目录/xxx(静态资源文件名),举例:http://localhost:8888/public.js
image.png

resourceProperties
resourceProperties的值在构造器初始化时设定了,来源于 ResourceProperties resourceProperties, WebProperties webProperties(前缀spring.resources和前缀spring.web配置文件
image.png
image.png
image.png
可以看到resourceProperties 是Resources类对象,
Resources 构造器进行初始化时,初始化了一个参数:CLASSPATH_RESOURCE_LOCATIONS(静态资源地址)
可以看到 这个参数(静态资源地址)的值有四个:
classpath:/META-INF/resources/,
classpath:/resources/,
classpath:/static/,
classpath:/public/
因此我们可以在这个四个地址底下放静态资源,我们可以直接通过 /**方式直接访问

三、结论

1、所有 /webjars/ ,都去 classpath:/META-INF/resources/webjars/ 找资源;
举例:http://localhost:8888/webjars/jquery/3.3.1/jquery.js
2、所有 / 都去默认四个地址下找资源
四个地址:
classpath:/META-INF/resources/,
classpath:/resources/,
classpath:/static/,
classpath:/public/
访问优先级 从高到低
举例:http://localhost:8888/public.js

四、实践结论

1、实践 /webjars/**

1)去webjars官网找 jquery 最近版本的pom依赖
image.png
image.png
2)将 依赖加入我们springboot项目的pom.xml 文件
image.png

3)启动springboot项目,访问:
http://localhost:7029/webjars/jquery/3.6.0/jquery.js
image.png
成功

2、实践 /** 及优先级

1)在resources下 建立四个路径,分别建立 同名文件 a.js ,js文件的内容如图:
image.png
启动项目,访问:

image.png

classpath:/META-INF/resources/, 路径优先级最高,文件最先被访问到
2)删掉classpath:/META-INF/resources/下的文件,再启动项目访问
image.png
classpath:/resources/, 优先级第2
3)删掉classpath:/resources/下的文件,再启动项目访问
image.png
classpath:/static/,优先级第3
classpath:/public/ 优先级最后

五、参考资料

找到了springboot1.x 的源码解读,跟 spring2.x 有点差别,但是核心代码和逻辑没有变
https://blog.csdn.net/cristianoxm/article/details/112639177