默认静态资源配置

  1. // 用於靜態資源的路徑模式。默认为/**
  2. spring.mvc.static-path-pattern=/**
  3. // 默认的静态资源位置
  4. // Locations of static resources. Defaults to classpath:[/META-INF/resources/, /resources/, /static/, /public/].
  5. spring.resources.static-locations=

静态资源配置原理

Springboot 2.x 对于静态资源的配置都是由 WebMvcAutoConfiguration 这个类完成的,我们这里选取 Springboot-2.4.4.RELEASE 这个版本来探究一下 Springboot 底层对于静态资源是如何进行配置的
首先来到 WebMvcAutoConfiguration 这个配置类,该类的声明如下

  1. // 配置中各个组件没有依赖关系,为了加快效率,设置 proxyBeanMethods=false ,其默认值是 true
  2. @Configuration(proxyBeanMethods = false)
  3. // 当前是否是 Web 环境,并且类型是否是 SERVLET 类型的
  4. @ConditionalOnWebApplication(type = Type.SERVLET)
  5. // 系统中包含 Servlet、DispatcherServlet、WebMvcConfigurer 这几个类
  6. @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
  7. // 容器中不包含 WebMvcConfigurationSupport 这个组件
  8. @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
  9. // 自动配置排序,数值越低,优先级越高
  10. @AutoConfigureOrder(-2147483638)
  11. // 自动配置在 DispatcherServletAutoConfiguration、TaskExecutionAutoConfiguration 、ValidationAutoConfiguration 之后
  12. @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
  13. public class WebMvcAutoConfiguration {
  14. public static final String DEFAULT_PREFIX = "";
  15. public static final String DEFAULT_SUFFIX = "";
  16. private static final String[] SERVLET_LOCATIONS = new String[]{"/"};
  17. public WebMvcAutoConfiguration() {
  18. }
  19. ....注册各种组件
  20. }

如果 WebMvcAutoConfiguration 上的条件注解都满足的情况下才会执行该类里面的各种行为,在该类中我们可以发现存在一个静态内部类 WebMvcAutoConfigurationAdapter ,该类的具体声明如下

  1. // 配置中各个组件没有依赖关系,为了加快效率,设置 proxyBeanMethods=false ,proxyBeanMethods 默认值是 true
  2. @Configuration(proxyBeanMethods = false)
  3. // 向容器中注册组件 EnableWebMvcConfiguration
  4. @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
  5. // 开启 WebMvcProperties、ResourceProperties 类上的 @ConfigurationProperties 注解,并且将这两个类注入 IOC 容器中
  6. @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
  7. // 排序
  8. @Order(0)
  9. public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
  10. private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
  11. // 1、静态资源配置类 ----> 详细见代码块一
  12. private final ResourceProperties resourceProperties;
  13. final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
  14. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  15. // 2、如果配置了 spring.resources.add-mappings=false ,那么 Springboot 就不会对静态资源和 webjars 进行自动配置
  16. // spring.resources.add-mappings 的默认值是 true,它就是一个是否开启 Springboot 对静态资源配置的开关,可以在
  17. // application.properties 中配置 debug=true ,然后就可以查看到 debug 级别的日志了
  18. if (!this.resourceProperties.isAddMappings()) {
  19. logger.debug("Default resource handling disabled");
  20. } else {
  21. // 3、获取缓存时间
  22. Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
  23. // 4、获取缓存控制对象
  24. CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
  25. // 5、webjars 的映射规则 /webjars/** ----> classpath:/META-INF/resources/webjars/
  26. // 也就是比如我们访问 http://localhost:8080/webjars/aaa.js ,实际上就是访问当前项目类路径下
  27. // /META-INF/resources/webjars/aaa.js 这个资源
  28. if (!registry.hasMappingForPattern("/webjars/**")) {this.customizeResourceHandlerRegistration(
  29. registry.addResourceHandler(new String[]{"/webjars/**"})
  30. .addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"})
  31. .setCachePeriod(this.getSeconds(cachePeriod))
  32. .setCacheControl(cacheControl));
  33. }
  34. // 6、staticPathPattern 默认值是 /** ,也可以在 application.properties 中配置 spring.mvc.static-path-pattern 来修改 Springboot 默认值
  35. String staticPathPattern = this.mvcProperties.getStaticPathPattern();
  36. // 7、静态资源映射规则
  37. if (!registry.hasMappingForPattern(staticPathPattern)) {
  38. this.customizeResourceHandlerRegistration(
  39. registry.addResourceHandler(new String[]{staticPathPattern})
  40. // 8、静态资源映射路径 ----> 详细见代码块二
  41. .addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations()))
  42. .setCachePeriod(this.getSeconds(cachePeriod))
  43. .setCacheControl(cacheControl));
  44. }
  45. }
  46. }
  47. }

代码块一、静态资源配置类

  1. // 1、将 application.properties 中 spring.resources 开头的配置项与 ResourceProperties 类的属性进行绑定
  2. // 如果该类中有属性没有匹配到值,则抛出异常
  3. @ConfigurationProperties(prefix = "spring.resources",ignoreUnknownFields = false)
  4. public class ResourceProperties {
  5. // 2、Springboot 默认的静态资源路径
  6. private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
  7. "classpath:/META-INF/resources/",
  8. "classpath:/resources/",
  9. "classpath:/static/",
  10. "classpath:/public/"
  11. };
  12. private String[] staticLocations;
  13. private boolean addMappings;
  14. private final ResourceProperties.Chain chain;
  15. private final ResourceProperties.Cache cache;
  16. public ResourceProperties() {
  17. // 2、Springboot 默认的静态资源路径
  18. this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
  19. // 3、addMappings 默认值为 true,也就是 application.properties 中不配置
  20. // spring.resources.add-mappings 选项,那么就使用 Springboot 的默认值 true
  21. this.addMappings = true;
  22. this.chain = new ResourceProperties.Chain();
  23. this.cache = new ResourceProperties.Cache();
  24. }
  25. public String[] getStaticLocations() {
  26. return this.staticLocations;
  27. }
  28. ...........
  29. }

代码块二
WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())
this.resourceProperties.getStaticLocations():获取与 ResourceProperties 绑定的静态资源路径, Springboot 默认的静态资源路径如下:

  1. private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
  2. "classpath:/META-INF/resources/",
  3. "classpath:/resources/",
  4. "classpath:/static/",
  5. "classpath:/public/"
  6. }

如果我们手动在 application.properties 中配置了 spring.resources.static-locations ,那么它会覆盖 Springboot 的默认值

总结

1、Springboot webjars 的默认映射规则 /webjars/ ——> classpath:/META-INF/resources/webjars/
2、Springboot 静态资源的默认映射规则 /
——> { classpath:/META-INF/resources、classpath:/resources/、classpath:/static/、classpath:/public/ }
静态资源的映射路径,优先级顺序为:META-INF/resources > resources > static > public
3、可以通过修改 application.properties 配置文件中的配置项来改变 Springboot 默认的配置规则,或者手动注册组件的方式来额外增加配置规则(与默认规则一同生效)

案例

1、webjars : 以依赖的方式引入 CSS、JS 等

登录 webjars 官网
https://www.webjars.org/
选择你需要的资源,这里以 js 为例

Springboot 静态资源配置 - 图1
引入后的 webjars 依赖如下

Springboot 静态资源配置 - 图2
由于 webjars 的映射规则是 /webjars/** ——> classpath:/META-INF/resources/webjars/
如果我想访问上图中的 classpath:/META-INF/resources/webjars/jquery/3.5.1/jquery.js 这个资源
那么浏览器只需要访问 http://localhost:8080/webjars/jquery/3.5.1/jquery.js 即可

2、静态资源

Springboot 对静态资源的默认映射规则是 /** ——> { classpath:/META-INF/resources、classpath:/resources/、classpath:/static/、classpath:/public/ }
例如浏览器发起请求 http://localhost:8080/aaa.txt ,那么就会去 classpath:/META-INF/resources、classpath:/resources/、classpath:/static/、classpath:/public/ 这几个目录中寻找有没有 aaa.txt 这个资源,如果有就返回结果,没有显示 404

3、自定义对静态资源的处理

3.1 修改 application.properties 配置文件的方式

注意:如果在配置文件中进行如下配置,则 springboot 默认配置将被覆盖,原先默认的静态路径访问前缀 /** 和默认的静态资源文件目录都将失效

  1. # 该配置项的默认值是 true,如果设置为 false 则代表关闭 Springboot webjars 和静态资源的配置
  2. # 如果配置了该项的话,可以开启 debug 日志级别,控制台可以看到禁用了 Springboot 的默认配置
  3. spring.resources.add-mappings=false
  4. # 开启 debug 级别的日志信息
  5. debug=true
  6. # 修改静态资源访问前缀,配置了该选项之后,比如你想访问静态资源文件夹下的 aaa.txt,需要带上 bluefatty 前缀
  7. # 例如:http://localhost:8080/bluefatty/aaa.txt 就是访问静态资源文件夹下的 aaa.txt
  8. # 配置了该配置项之后,Springboot 默认的首页访问将失效
  9. spring.mvc.static-path-pattern=/bluefatty/**
  10. # 修改 Springboot 默认的静态资源文件夹,启用了该配置之后 Springboot 默认的静态资源文件夹将失效
  11. spring.resources.static-locations=classpath:/META-INF/resources02/,\
  12. classpath:/resources02/,\
  13. classpath:/static02/,\
  14. classpath:/public02/,\
  15. file:D://xiaomaomao/

3.2 代码注册的方式

注意:如果使用 WebMvcConfigurer 进行自定义规则,那么原先 Springboot 默认的配置和我们自定义的配置将一同起效
如果你不想使用配置文件,我们还可以通过代码的方式来进行自定义,具体步骤如下
自定义一个配置类 xxxWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter(或者实现 WebMvcConfigurer ,其实是一个意思,因为 WebMvcConfigurerAdapter 实现了 WebMvcConfigurer 接口)

  1. @Configuration
  2. public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
  3. @Override
  4. public void addResourceHandlers(ResourceHandlerRegistry registry){
  5. registry.addResourceHandler("/**")
  6. // 1、src/main/webapp/ 路径下
  7. .addResourceLocations("resources/", "static/", "public/",
  8. "META-INF/resources/")
  9. // 2、src/main/resources/ 路径下
  10. .addResourceLocations("classpath:resources/", "classpath:static/",
  11. "classpath:public/", "classpath:META-INF/resources/")
  12. // 3、绝对路径
  13. .addResourceLocations("file:D://xiaomaomao/");
  14. }
  15. }

上面的配置有三种路径方式 static/ (无前缀)、带有 classpath 前缀、绝对路径,这里来简单说明一下
1、无前缀 ——> “文档根目录”(一般指代 src/main/webapp 目录),例如 localhost:8080/index.html 定位至 src/main/webapp/static/index.html
2、存在前缀 classpath ——-> 类路径(一般指代 src/main/resources 目录)
3、存在前缀 file:// ——-> 文件系统路径(“绝对路径”)

接管Spring Boot的Web配置

如果Spring Boot提供的Sping MVC不符合要求,则可以通过一个配置类(注解有@Configuration的类)加上@EnableWebMvc注解来实现完全自己控制的MVC配置。
当然,通常情况下,Spring Boot的自动配置是符合我们大多数需求的。在你既需要保留Spring Boot提供的便利,有需要增加自己的额外的配置的时候,可以定义一个配置类并继承WebMvcConfigurerAdapter,无需使用@EnableWebMvc注解。
这里我们提到这个WebMvcConfigurerAdapter这个类,重写这个类中的方法可以让我们增加额外的配置,这里我们就介绍几个常用的。

自定义资源映射addResourceHandlers

比如,我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可。

  1. @Configuration
  2. public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
  3. /**
  4. * 配置静态访问资源
  5. * @param registry
  6. */
  7. @Override
  8. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  9. registry.addResourceHandler("/my/**").addResourceLocations("classpath:/my/");
  10. super.addResourceHandlers(registry);
  11. }
  12. }

通过addResourceHandler添加映射路径,然后通过addResourceLocations来指定路径。
如果你想指定外部的目录也很简单,直接addResourceLocations指定即可,代码如下:

  1. @Override
  2. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  3. registry.addResourceHandler("/my/**").addResourceLocations("file:E:/my/");
  4. super.addResourceHandlers(registry);
  5. }

addResourceLocations指的是文件放置的目录,addResoureHandler指的是对外暴露的访问路径

页面跳转addViewControllers

以前写SpringMVC的时候,如果需要访问一个页面,必须要写Controller类,然后再写一个方法跳转到页面,感觉好麻烦,其实重写WebMvcConfigurerAdapter中的addViewControllers方法即可达到效果了

  1. /**
  2. * 以前要访问一个页面需要先创建个Controller控制类,再写方法跳转到页面
  3. * 在这里配置后就不需要那么麻烦了,直接访问http://localhost:8080/toLogin就跳转到login.jsp页面了
  4. * @param registry
  5. */
  6. @Override
  7. public void addViewControllers(ViewControllerRegistry registry) {
  8. registry.addViewController("/toLogin").setViewName("login");
  9. super.addViewControllers(registry);
  10. }

在这里重写addViewControllers方法,并不会覆盖WebMvcAutoConfiguration中的addViewControllers(在此方法中,Spring Boot将“/”映射至index.html),这也就意味着我们自己的配置和Spring Boot的自动配置同时有效,这也是我们推荐添加自己的MVC配置的方式。

拦截器addInterceptors

拦截器在我们项目中经常使用的,这里就来介绍下最简单的判断是否登录的使用。
要实现拦截器功能需要完成以下2个步骤:

  • 创建我们自己的拦截器类并实现 HandlerInterceptor 接口
  • 其实重写WebMvcConfigurerAdapter中的addInterceptors方法把自定义的拦截器类添加进来即可

首先,自定义拦截器代码:

  1. package com.dudu.interceptor;
  2. public class MyInterceptor implements HandlerInterceptor {
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. boolean flag =true;
  6. User user=(User)request.getSession().getAttribute("user");
  7. if(null==user){
  8. response.sendRedirect("toLogin");
  9. flag = false;
  10. }else{
  11. flag = true;
  12. }
  13. return flag;
  14. }
  15. @Override
  16. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  17. }
  18. @Override
  19. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  20. }
  21. }

这里我们简单实现了根据session中是否有User对象来判断是否登录,为空就跳转到登录页,不为空就通过。
接着,重写WebMvcConfigurerAdapter中的addInterceptors方法如下:

  1. /**
  2. * 拦截器
  3. * @param registry
  4. */
  5. @Override
  6. public void addInterceptors(InterceptorRegistry registry) {
  7. // addPathPatterns 用于添加拦截规则
  8. // excludePathPatterns 用户排除拦截
  9. registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/toLogin","/login");
  10. super.addInterceptors(registry);
  11. }

addPathPatterns("/**")对所有请求都拦截,但是排除了/toLogin/login请求的拦截。