在 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 {
// WebMvcAutoConfigurationAdapter 构造器, 参数详解见下段代码
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) {
// addMappings(是否默认的资源映射): 默认为true, 所以此if进不去
// 如果不想访问静态资源, 则在配置文件中设置 add-mappings: false
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
// -----静态资源默认配置规则-----
// 获取静态资源缓存时间, 默认为0
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
/* 设置 /webjars/** 规则
* 如果访问此路径, 则在 classpath:/META-INF/resources/webjars/ 路径下寻找对应的静态资源,
* 找到后并缓存之前设置的缓存时间
*/
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
// 获取自动注入类 WebMvcProperties 的属性 staticPathPattern, 默认为 "/**", 可以通过配置文件修改
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 是否给此路径配置了处理程序, 显然, "/**" 没有对应的处理程序
if (!registry.hasMappingForPattern(staticPathPattern)) {
// 和webjars处理规则一样, 去getResourceLocations(this.resourceProperties.getStaticLocations())获取资源路径
// 详解见下段代码
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; } // 其中 SERVLET_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请求