SpringBoot对静态资源URL映射的初始化
DelegatingWebMvcConfiguration是一个springboot的MVC配置管理类,继承父类WebMvcConfigurationSupport
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 自动装配实现了WebMvcConfigurer的配置bean
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
// 装配到WebMvcConfigurerComposite
this.configurers.addWebMvcConfigurers(configurers);
}
}
/** ...省略若干 */
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
this.configurers.addResourceHandlers(registry);
}
/** ...省略若干 */
}
DelegatingWebMvcConfiguration在初始化时将所有实现了WebMvcConfigurer接口的配置bean装配到WebMvcConfigurerComposite中,在各个配置方法中均间接调用了WebMvcConfigurerComposite的配置方法。由于其继承了WebMvcConfigurationSupport所以在初始化时也会产生各种配置bean其中就包含了一个对静态资源处理的bean:
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
* {@link #addResourceHandlers}.
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper());
// 此处初始化对静态资源处理的ResourceHandlerRegistry实例(在父类中是抽象方法,此处调用的是DelegatingWebMvcConfiguration自己的实现)
addResourceHandlers(registry);
// 此处初始化url -> handler映射的HandlerMapping实例(实际是SimpleUrlHandlerMapping)
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping != null) {
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
// 如果registry中没有注册任何url映射则会返回默认HandlerMapping实例(即EmptyHandlerMapping)
// 这个EmptyHandlerMapping不会处理任何url均映射
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
下面主要从3个点来分析一下resourceHandlerMapping构建HandlerMapping实例的过程:
初始化ResourceHandlerRegistry
DelegatingWebMvcConfiguration初始化ResourceHandlerRegistry实例是通过addResourceHandlers()方法,实际是调用了WebMvcConfigurerComposite的addResourceHandlers()方法并对所有WebMvcConfigurer实例调用addResourceHandlers()方法,如下:
/**
* A {@link WebMvcConfigurer} that delegates to one or more others.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
/** 省略若干... */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addResourceHandlers(registry);
}
}
/** 省略若干... */
}
springboot提供了一个WebMvcAutoConfiguration配置bean,对springMVC做了默认配置定制,其中的静态内部类WebMvcAutoConfigurationAdapter实现了WebMvcConfigurer并在addResourceHandlers方法中对静态资源的ResourceHandlerRegistry进行了初始化:
// Defined as a nested config to ensure WebMvcConfigurer is not read when not on the classpath
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final HttpMessageConverters messageConverters;
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ResourceLoader resourceLoader;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
@Lazy HttpMessageConverters messageConverters,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConverters = messageConverters;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
}
/** ...省略若干 */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache()
.getCachecontrol().toHttpCacheControl();
// 添加对webjars资源处理的映射
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
// 添加对静态资源处理的映射
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry
// 静态资源映射路径:/**
.addResourceHandler(staticPathPattern)
// 映射静态资源位置:locations:classpath:/META-INF/resources/,
// classpath:/resources/, classpath:/static/, classpath:/public/, /
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl));
}
}
/** ...省略 */
}
初始化HandlerMapping
主要是通过ResourceHandlerRegistry的getHandlerMapping()方法,将每一个注册在ResourceHandlerRegistry的url映射转换为<url, ResourceHttpRequestHandler>映射交给SimpleUrlHandlerMapping的实例管理,其中ResourceHttpRequestHandler封装了响应url的静态资源的位置。
/**
* Encapsulates information required to create a resource handler.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author Brian Clozel
* @since 3.1
*/
public class ResourceHandlerRegistration {
private final String[] pathPatterns;
private final List<String> locationValues = new ArrayList<>();
@Nullable
private Integer cachePeriod;
@Nullable
private CacheControl cacheControl;
@Nullable
private ResourceChainRegistration resourceChainRegistration;
/**
* Create a {@link ResourceHandlerRegistration} instance.
* @param pathPatterns one or more resource URL path patterns
*/
public ResourceHandlerRegistration(String... pathPatterns) {
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling.");
this.pathPatterns = pathPatterns;
}
/**
* Add one or more resource locations from which to serve static content.
* Each location must point to a valid directory. Multiple locations may
* be specified as a comma-separated list, and the locations will be checked
* for a given resource in the order specified.
* <p>For example, {
{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}}
* allows resources to be served both from the web application root and
* from any JAR on the classpath that contains a
* {@code /META-INF/public-web-resources/} directory, with resources in the
* web application root taking precedence.
* <p>For {@link org.springframework.core.io.UrlResource URL-based resources}
* (e.g. files, HTTP URLs, etc) this method supports a special prefix to
* indicate the charset associated with the URL so that relative paths
* appended to it can be encoded correctly, e.g.
* {@code [charset=Windows-31J]http://example.org/path}.
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
this.locationValues.addAll(Arrays.asList(resourceLocations));
return this;
}
/**
* Specify the cache period for the resources served by the resource handler, in seconds. The default is to not
* send any cache headers bu