问题
既然Spring Boot 提供了web场景的启动器,那么它是如何去处理静态资源的呢?我们的静态资源存放在什么位置呢?
SpringBoot给我们提供了大量的自动配置类,而WebMVCAutoConfiguration就是Spring Boot给我们提供来支持web开发的自动配置类。
静态资源处理规则
在过去,我们项目的静态资源(如css,js等文件)是放在webapp下,现在pom打包方式是以jar的方式,那么Spring Boot是如何去处理对于静态资源存放的问题呢?
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面,可以找到一个addResourceHandlers()
的方法,该方法用于资源处理
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
//已禁用默认资源处理
logger.debug("Default resource handling disabled");
return;
}
//webjars配置
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//静态资源配置
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
通过源码分析可知:访问所有的/webjars/**,都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源
我们在静态资源配置那里继续点击getStaticPathPattern()
方法
public String getStaticPathPattern() {
return this.staticPathPattern;
}
private String staticPathPattern = "/**";
发现了静态资源配置的路径是"/**",访问当前项目的任意资源,它会去寻找resourceProperties这个类的静态方法getStaticLocations()
,我们点进去进行分析
public String[] getStaticLocations() {
return this.staticLocations;
}
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
ResourceProperties的静态方法getStaticLocations包含着Spring Boot会去寻找资源的文件夹名,保存在String数组中。
结论 四个目录可以存放的静态资源被Spring Boot识别
1. "classpath:/META-INF/resources/"
2. "classpath:/resources/"
3. "classpath:/static/"
4. "classpath:/public/"
可以在resources根目录下新建对应的文件夹,来存放我们的静态文件;在application.properties中配置;
自定义静态资源
我们可以指定配置文件,哪些文件夹用来存放静态资源
spring.web.resources.static-locations="classpath:liang/"
Spring Boot默认为我们提供3种静态资源映射规则:
WebJars映射
过去我们为了让页面更加美观,让用户有更好的体验感,Web应用提供了大量的JS和CSS,例如 jQuery,Backbone.js 和 Bootstrap 等等,放在webapp下管理,但Spring Boot项目是以JAR包的形式进行部署的,不存在webapp目录,此时WebJars就出现了,它以Jar形式为Web项目提供资源文件。
WebJars 可以将 Web 前端资源(JS,CSS 等)打成一个个的 Jar 包,然后将这些 Jar 包部署到 Maven 中央仓库中进行统一管理,当 Spring Boot 项目中需要引入 Web 前端资源时,只需要访问 WebJars 官网,找到所需资源的 pom 依赖,将其导入到项目中即可。
下图展示如何通过 WebJars 查找 JQuery 的 pom 依赖的过程。
webjar打包的依赖
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jqurey</artifactId>
<version>3.1.1</version>
</dependency>
代码实现
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jqurey</artifactId>
<version>3.1.1</version>
</dependency>
默认静态资源映射
当访问项目中的任意资源(即"/**")时,Spring Boot会默认从以下路径中查找资源文件(优先级依次降低)
这些路径又被称为静态资源文件夹,当我们请求某个静态资源时,Spring Boot会从这些存放静态资源的路径下去寻找,从优先级高的找到优先级低的,知道找到指定的静态资源为止。
存在在不同文件下的静态资源的优先级
创建上述四个文件夹
/META-INF/resources/创建1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>/META-INF/resources</h1>
</body>
</html>
/resources/创建1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>resources</h1>
</body>
</html>
/static/创建1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>static</h1>
</body>
</html>
/public/创建1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>public</h1>
</body>
</html>
通过比较可以比较出它们之间的优先级。当我们请求某个静态资源(即以“.html”结尾的请求)时,Spring Boot 会先查找优先级高的文件夹,再查找优先级低的文件夹,直到找到指定的静态资源为止。
静态首页(欢迎页)映射
静态资源文件夹下的所有 index.html 被称为静态首页或者欢迎页,它们会被 /** 映射,换句话说就是,当我们访问"localhost:8080"时,都会跳转到静态首页(欢迎页)
注意,访问静态首页或欢迎页时,其查找顺序也遵循默认静态资源的查找顺序,
即先查找优先级高的目录,在查找优先级低的目录,直到找到 index.html 为止。
静态页面映射原理
WelcomePageHandlerMapping()
方法 public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
//欢迎页面映射
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
/*getWelcomePage()得到欢迎页面,getStaticPathPattern()得到一个/**,
通过/**映射跳转到欢迎页*/
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
//设置拦截器
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
//设置其他配置
return welcomePageHandlerMapping;
}
点开getWelCome()
方法
//返回一个首页资源
private Resource getWelcomePage() {
for (String location : this.resourceProperties.getStaticLocations()) {
//遍历所有的静态资源文件
Resource indexHtml = getIndexHtml(location);
//判断是否是index.html页面
if (indexHtml != null) {
return indexHtml;
}
}
//上下文内容
ServletContext servletContext = getServletContext();
if (servletContext != null) {
return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
//创建一个上文资源,通过/映射
}
return null;
}
点开getIndexHtml()方
法
private Resource getIndexHtml(String location) {
return
//this.resourceLoader.getResource(location),加载静态的资源
getIndexHtml(this.resourceLoader.getResource(location));
}
private Resource getIndexHtml(Resource location) {
try {
//判断index.html是否存在
Resource resource = location.createRelative("index.html");
if (resource.exists() && (resource.getURL() != null)) {
return resource;
}
}
catch (Exception ex) {
}
return null;
}
通过上述,得出结论
静态首页映射的原理是Spring Boot去扫描静态资源目录下的index.html页面,同时遵循静态资源优先级原则。
测试
在项目public 路径下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>