在 SpringBoot 中, 默认访问主页(index.html)可以配置在 resources/static or resources/templates 下, 容器启动后, 可以默认去访问 index.html 文件, 其中的原理是什么?
默认访问规则
SpringBoot启动时会加载 xxxAutoConfiguration 类 (自动配置类), 关于 SpringMvc 的自动配置类是 WebMvcAutoConfiguration
@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 {
}
-
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) 当容器中存在 Servlet, DispatcherServlet, WebMvcConfigurer 类时, WebMvcAutoConfiguration 才会生效, 显然是存在的 -
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 当容器中不存在 WebMvcConfigurationSupport 才会生效
那么WebMvcAutoConfiguration中干了什么, 容器启动时才可以默认访问 Index.html 静态资源
我们发现 WebMvcAutoConfiguration 类中有一个实现了 WebMvcConfigurer 的接口 -> WebMvcAutoConfigurationAdapter 静态内部类
@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:
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
}
跟配置文件 spring.mvc 开头的属性绑定
ResourceProperties:
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
}
跟配置文件 spring.resources 开头的属性绑定
当上述条件都生效, 则意味着WebMvcAutoConfigurationAdapter 也会去执行自己的逻辑
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> r,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations)
{
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = r.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
@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();
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)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
}
WebMvcAutoConfigurationAdapter 构造器参数:
- ResourceProperties: 对应WebMvcAutoConfiguration 注入的 ResourceProperties, 绑定了 spring.resources 的值
- WebMvcProperties: 对应WebMvcAutoConfiguration 注入的 WebMvcProperties, 绑定了 spring.mvc 的值
- ListableBeanFactory: Bean 容器, 实现了 BeanFactory
- ObjectProvider<HttpMessageConverters>: 消息转换器
- ObjectProvider<ResourceHandlerRegistrationCustomizer>: 自定义资源处理器
- ObjectProvider<DispatcherServletPath>: 请求分发处理器
- ObjectProvider<ServletRegistrationBean<?>>: 给应用注册原生的 Servlet 等组件
/** 资源访问规则(getResourceLocations(this.resourceProperties.getStaticLocations())):
this.resourceProperties.getStaticLocations():
resourceProperties 对应上文所注入的 ResourceProperties, getStaticLocations ?????获取到对应的值为静态常量: CLASSPATH_RESOURCE_LOCATIONS
CLASSPATH_RESOURCE_LOCATIONS = ????????????????????????????????????????{ ??????????????????????????????????????????“classpath:/META-INF/resources/”, ??????????????????????????????????????????“classpath:/resources/”, ??????????????????????????????????????????“classpath:/static/”, ??????????????????????????????????????????“classpath:/public/” ????????????????????????????????????????};
getResourceLocations(this.resourceProperties.getStaticLocations()):
那 getResourceLocations 又干了什么事呢?
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
这个时候我们明白了, "/**"的访问规则有 5 种
分别是: “/”, “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”
注意: "/" 代表项目目录下的 webapp 文件夹
所以SpringBoot静态资源默认访问是 “/”, “classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”
欢迎页访问规则
讲完了SpringBoot默认静态资源访问规则, 再来看看欢迎页的规则
在 WebMvcAutoConfiguration 中注册了 WelcomePageHandlerMapping Bean
@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;
}
打开 WelcomePageHandlerMapping 构造器
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Optional<Resource> welcomePage, String
staticPathPattern) {
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");
}
}
可以看到, 当欢迎页存在并且请求为 “/**” 时, 就转发到index.html页面, 否则会去Controller中去寻找 index请求
|