SpringBoot静态资源配置原理

SpringBoot中与静态资源配置有关的代码在自动配置类WebMvcAutoConfiguration里(org.springframework.boot.autoconfigure.web.servlet包下)。

1
2
3
4
5
6
7
8
9
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}

其中有一个重要的静态内部类WebMvcAutoConfigurationAdapter是静态资源加载原理的核心。

1
2
3
4
5
6
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
}

@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })注解使配置文件与WebMvcProperties类和ResourceProperties类进行绑定后分别转换为bean,从源码可以看出WebMvcProperties和spring.mvc为前缀的配置相关联,ResourceProperties和spring.resources为前缀的配置相关联。

1
2
3
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
}
1
2
3
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
}

由于WebMvcAutoConfigurationAdapter只有一个有参构造器,说明其构造器中传入的值都是从ioc中获取的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
//从容器中找到ResourceProperties组件(和spring.resource绑定的所有的值的对象)
this.resourceProperties = resourceProperties;
//从容器中找到WebMvcProperties组件(和spring.mvc绑定的所有的值的对象)
this.mvcProperties = mvcProperties;
//从容器中找到Spring的bean工厂
this.beanFactory = beanFactory;
//从容器中找到所有的消息转换器
this.messageConvertersProvider = messageConvertersProvider;
//从容器中找到资源处理器的自定义器
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
//从容器中找到dispatcherServlet能处理的路径
this.dispatcherServletPath = dispatcherServletPath;
//从容器中找到能给应用注册Servlet、Filter的组件
this.servletRegistrations = servletRegistrations;
}

资源处理的默认规则配置在addResourceHandlers()中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//如果在配置文件中配置spring.resources.add-mappings=false则禁用所有静态资源规则
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//可通过设置spring.resources.cache.period的值来配置静态资源缓存时间
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
//请求是/webjars/**时会到类路径的/META-INF/resources/webjars/下找寻静态资源并返回给用户,并设置上面设定的缓存时间
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//获取到spring.mvc.static-path-pattern的配置,默认是/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//请求是staticPathPattern时会映射到this.resourceProperties.getStaticLocations()目录下,即{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" },同时并设置上面设定的缓存时间
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
}

欢迎页的处理规则在另一个静态内部类EnableWebMvcConfiguration中。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
//如果欢迎界面存在并且/**是staticPathPattern(默认是/**)则转发到欢迎界面,这里说明如果我们更改了默认的spring.mvc.static-path-pattern=/**则不会转发到欢迎界面
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage.get());
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
}