Springboot 2.x 对于欢迎页的配置都是由 WebMvcAutoConfiguration 这个类完成的,我们这里选取 Springboot-2.3.7.RELEASE 这个版本来探究一下 Springboot 底层对于欢迎页是如何定义的
一、欢迎页配置原理
在 WebMvcAutoConfiguration 配置类中有一个静态内部类 EnableWebMvcConfiguration ,由该类完成对 Springboot 欢迎页的配置
// 1、标记该类是个配置类, proxyBeanMethods 默认值为 true , 由于组件之间没有依赖关系,为了加快 Spring 检索效率,设置其值为 false @Configuration(proxyBeanMethods = false) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { // 成员变量 private final ResourceProperties resourceProperties; private final WebMvcProperties mvcProperties; private final ListableBeanFactory beanFactory; private final WebMvcRegistrations mvcRegistrations; private ResourceLoader resourceLoader; // 2、这里有一个知识点,由于该类只有一个有参数的构造方法,所以构造方法中的参数值都是从 IOC 容器中获取的 public EnableWebMvcConfiguration( // 2.1、与 ResourceProperties 进行配置绑定 ----> 详情见代码块一 ResourceProperties resourceProperties, // 2.2、与 WebMvcProperties 进行配置绑定 ----> 详情见代码块二 ObjectProvider<WebMvcProperties> mvcPropertiesProvider, // 2.3、Web 模块注册的接口 ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, // 2.4、IOC 容器 ListableBeanFactory beanFactory) { // 2.5、从 IOC 容器中获取到组件,并且将这些组件赋值给 EnableWebMvcConfiguration 的成员变量 this.resourceProperties = resourceProperties; this.mvcProperties = mvcPropertiesProvider.getIfAvailable(); this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); this.beanFactory = beanFactory; } // 3、向容器中注册欢迎页处理器映射组件 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { // 3.1、获取欢迎页处理器映射 -----> 详情见代码块七 WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, // 3.2、获取欢迎页面 ----> 详情见代码块三 getWelcomePage(), // 3.3、获取静态资源前缀 ----> 详情见代码块六 this.mvcProperties.getStaticPathPattern()); // 3.4、设置拦截器 welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); // 3.5、设置 CorsConfiguration welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); // 3.6、返回欢迎页处理器映射 return welcomePageHandlerMapping; } private Optional<Resource> getWelcomePage() { String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); } private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); } .......注册其它的一些组件 }
代码块一、与 ResourceProperties 进行配置绑定
打开 ResourceProperties 这个类,该类的声明如下
// 1、与 application.properties 配置文件中以 spring.resources 开头的配置项进行属性绑定
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
// 2、默认的静态资源路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 3、是否开启默认的静态资源配置
private boolean addMappings = true;
private final Chain chain = new Chain();
private final Cache cache = new Cache();
......
}
代码块二、与 WebMvcProperties 进行配置绑定
打开 WebMvcProperties 这个类,该类的声明如下
// 1、与 application.properties 配置文件中以 spring.mvc 开头的配置项进行属性绑定
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
/**
* Formatting strategy for message codes. For instance, `PREFIX_ERROR_CODE`.
*/
private DefaultMessageCodesResolver.Format messageCodesResolverFormat;
/**
* Locale to use. By default, this locale is overridden by the "Accept-Language"
* header.
*/
private Locale locale;
......
}
代码块三、getWelcomePage():获取欢迎页面
private Optional<Resource> getWelcomePage() {
// 1、this.resourceProperties.getStaticLocations():获取静态资源路径 ----> 详情见代码块四
// 2、getResourceLocations:获取最终的映射路径 ----> 详情见代码块五
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
// 3、通过筛选、过滤等操作,遍历上述所有的路径查看这些路径是否存在 index.html 页面,如果有则将该页面作为可选择的欢迎页面
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
代码块四、this.resourceProperties.getStaticLocations()
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
// 1、Springboot 默认的静态资源路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
public String[] getStaticLocations() {
return this.staticLocations;
}
......
}
代码块五、getResourceLocations(...)
static String[] getResourceLocations(String[] staticLocations) {
// 1、创建一个 String 类型的路径数组,其长度为静态资源路径长度 + 1
// staticLocations 为静态资源路径,SERVLET_LOCATIONS 为常量 / ,其长度为 1
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
// 2、将静态资源路径数组复制到新数组中
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
// 3、将 SERVLET_LOCATIONS 复制到新数组中
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
// 4、返回新的路径数组,该数组包含了 springboot 的静态资源路径和 /
return locations;
}
代码块六、this.mvcProperties.getStaticPathPattern()
// 1、该配置类中的属性与 application.properties 中的 spring.mvc 开头的配置项动态绑定
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
// 2、staticPathPattern 默认值为 /** ,可以在配置 spring.mvc.static-path-pattern 来替换默认值 /**
private String staticPathPattern = "/**";
public String getStaticPathPattern() {
return this.staticPathPattern;
}
......
}
代码块七、new WelcomePageHandlerMapping(...)
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) { // 1、首先判断类路径和静态资源路径下是否存在 index.html 页面 // 代码块三 第 3 小点中,如果静态资源路径和类路径 / 下面有 index.html 页面,那么 welcomePage.isPresent() 为 true // staticPathPattern 的值必须要为 /** ,如果我们改变了 staticPathPattern 的值,那么首页的配置将失效 if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage.get()); setRootViewName("forward:index.html"); } // 2、如果上述条件不成立,接着才会从模板引擎下寻找是否存在 index.html // 例如 Springboot 推荐的 thymeleaf (即 resources/templates/ 文件夹下是否存在 index.html ,如果有就把其作为首页 else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
二、总结
1、默认情况下 Springboot 会去 /、 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/ 这五个路径下寻找是否存在 index.html ,如果存在就当其作为首页,
2、如果 staticPathPattern 的值改变了,那么首页配置将失效(注意,如果你想使用 Springboot 的首页功能,就不要去配置文件中配置 spring.mvc.static-path-pattern 选项 )
3、上述五个路径下如果找不到 index.html , Springboot 会尝试去模板引擎特定的文件夹下寻找 index.html ,如果存在就将其作为首页
三、案例
通过上面的分析,我们可以进行自定义静态资源的访问路径和首页的访问路径
注意:如果我们修改了 Springboot 的配置文件 application.properties 中的配置项,那么 Springboot 的默认值就会被覆盖
1、application.properties 不进行任何配置,全部使用 Springboot 默认的配置
可以看到,只要你将 index.html 放置在 Springboot 的默认静态资源文件夹下, Springboot 就能够识别到 index.html ,并且将其作为首页
2、修改 application.properties 中配置(修改了静态资源访问前缀、修改了默认的静态资源文件夹) 在 classpath:/static2/ 路径下放置 index.html
# 给静态资源访问配置一个前缀 /bluefatty/ ,配置了该配置项之后,Springboot 默认的首页访问将失效
spring.mvc.static-path-pattern=/bluefatty/**
# 修改 Springboot 默认的静态资源文件夹,启用了该配置之后 Springboot 默认的静态资源文件夹将失效
spring.resources.static-locations=classpath:/static2/,
classpath:/public2/
浏览器发起请求 http://localhost:8080/ 访问 index.html ,则会出现如下报错
为什么会出现这种原因呢? 因为我们在 application.properties 配置文件中修改了 staticPathPattern 的值,在不使用模板引擎的情况下 staticPathPattern 的值必须要为 /** (这个可以从代码块七中找到答案)否则就无法定位到静态资源文件夹下的 index.html 了
3、自定义配置
需求如下
上述需求该如何实现呢? 我们可以分析一下
通过 http://localhost:8080/ 能访问到首页,那么我们就不能改变 staticPathPattern 的值,因为改变了它的值之后代码七中的判断条件不成立,也就不能映射到静态资源文件夹下的 index.html,也就是我们就不能在 application.properties 中配置 spring.mvc.static-path-pattern 选项,因为在配置文件中配置的值会覆盖 Springboot 默认值
通过 http://localhost:8080/banner/xiaomaomao.txt 能访问到资源,那么就需要保留 Springboot 默认的静态资源文件夹,也就是不能在 application.properties 中配置 spring.resources.static-locations ,因为配置了该选项之后,Springboot 默认的静态资源文件夹会被覆盖,从而失效
通过 http://localhost:8080/slas/images/tiger.jpg 和 http://localhost:8080/slas/txt/Mango.txt 能够访问到对应的资源,我们可以考虑给其扩展额外的映射路径
综上:我们不能在 application.properties 中配置 spring.mvc.static-path-pattern 和 spring.resources.static-locations ,并且需要额外扩展静态资源的映射路径
那么只需要定义一个配置类
@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
@Override
// 添加静态资源映射路径,它不会改变 Springboot 默认的值它会和 Springboot 默认的
// /** 映射 {classpath:/META-INF/resources/、classpath:/resources/、
// classpath:/static/、classpath:/public/}共同生效
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/slas/**")
.addResourceLocations("classpath:slasResources/static/");
}
}
测试都是生效的
4、改变首页的路径
需求如下
需要在 application.properties 中配置 spring.resources.static-locations ,配置之后 Springboot 默认的静态资源文件夹就被 /slasResources/static/ 替代了
# 修改 Springboot 默认的静态资源文件夹,启用了该配置之后 Springboot 默认的静态资源文件夹将失效
spring.resources.static-locations=classpath:/slasResources/static/
自定义配置类并注册组件
@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/slas/**")
.addResourceLocations("classpath:/static/");
}
}
测试如下