SpringBoot 静态资源原理

1,177 阅读3分钟

静态资源的配置原理在 WebMvcAutoConfiguration 这个自动配置类中定义了

WebMvcAutoConfiguration 的作用是定义一些 MVC 使用的 Bean 组件到容器中

比如这两个组件

// REST 请求的过滤器
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
   return new OrderedHiddenHttpMethodFilter();
}
​
// 表单过滤器
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
   return new OrderedFormContentFilter();
}

但我们要找的静态资源配置类在一个名为 WebMvcAutoConfigurationAdapter 的静态内部类中

类定义如下

@SuppressWarnings("deprecation") // SpringBoot 版本 2.6.3,显示过时
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
    // 里面只有一个构造方法
    public WebMvcAutoConfigurationAdapter(
                 // 获取和 "spring.web" 绑定的所有的值的对象,此处低版本的参数为 ResourceProperties resourceProperties
                 WebProperties webProperties, 
                 // 获取和 "spring.mvc" 绑定的所有的值的对象
                 WebMvcProperties mvcProperties,
                 // 获取 Spring 容器
                ListableBeanFactory beanFactory, 
                 // 消息转换器
                 ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                 // 资源处理器自定义器
                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                 // dispatcherServlet 能处理的路径
                ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                 // 给应用注册原生的 Servlet、Filter 等
                ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = webProperties.getResources(); // 底层写死了四个默认资源路径
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
            this.mvcProperties.checkConfiguration();
        }
}

只有一个构造方法,所以所有的方法参数都是从 Spring 容器中获取

引入的两个配置

1、WebMvcProperties.class

可以看出是跟配置文件 "spring.mvc" 开头的属性绑定的

7TQR6HFIJ57ERCK62489.png

2、WebProperties.class

和配置文件 "spring.web" 开头的属性绑定的

U5EAOLGR4HCL4.png

资源处理的默认规则

请求进来,先去找 Controller 看能不能处理,不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面

在静态内部类中的这个方法里

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
   }
   // 调用 1(处理 /webjars 的请求)
   addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
   // 调用 2 
   // this.mvcProperties.getStaticPathPattern():默认是 "/**" 
   addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
      // this.resourceProperties.getStaticLocations():默认是 "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/"
      registration.addResourceLocations(this.resourceProperties.getStaticLocations());
      if (this.servletContext != null) {
         ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
         registration.addResourceLocations(resource);
      }
   });
}

从这个方法中就可以看出了为什么默认的四个资源文件路径是:"classpath:/META-INF/resources/""classpath:/resources/""classpath:/static/""classpath:/public/" 这几个路径下的

因为底层已经帮我们写好了,若需要当然也可以自定义

这个方法在高版本做了解耦合,如将一些静态资源的缓存方案之类的功能,方法重载提取到了另一些方法中

// 1
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
   // 最终还是调用 2
   addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
​
// 2
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
      Consumer<ResourceHandlerRegistration> customizer) {
   if (registry.hasMappingForPattern(pattern)) {
      return;
   }
   ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
   customizer.accept(registration);
   // 静态资源缓存策略
   registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
   registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
   registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
   customizeResourceHandlerRegistration(registration);
}

可以在配置文件中指定静态资源的缓存时间,底层是 setCacheSeconds(this.cachePeriod)这个方法,可以看出单位是秒

欢迎页处理规则

欢迎页的处理规则在另一个静态内部类中,定义如下

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
    
}    

类中有关于欢迎页处理的方法,返回一个 欢迎页处理器映射器

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
      FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
   
   // 让我们看看这个 WelcomePageHandlerMapping 的构造方法
   WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
         new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
         this.mvcProperties.getStaticPathPattern()
   );
   welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
   welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
   return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping 构造方法

final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
    
    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
      ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    
      if (welcomePage != null && "/**".equals(staticPathPattern)) {
         logger.info("Adding welcome page: " + welcomePage);
         setRootViewName("forward:index.html");
      }
      else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
         logger.info("Adding welcome page template: index");
         setRootViewName("index");
      }
   }
}

这个判断,就解释了为什么配置了静态资源映射路径之后就访问不到欢迎页的问题了

底层已经写死了

ResourceHandlerRegistrationCustomizer 后面聊