環(huán)境:Springboot2.7.10
默認(rèn)情況下,Spring Boot從類路徑中的/static(或/public或/resources或/META-INF/resources)目錄或ServletContext的根目錄中提供靜態(tài)內(nèi)容。它使用來自Spring MVC的ResourceHttpRequestHandler,因此可以通過添加自己的WebMvcConfigurer并覆蓋addResourceHandlers方法來修改該行為。
默認(rèn)情況下,資源映射在/**上,但是你可以使用spring.mvc.static-path-pattern配置屬性進(jìn)行修改。例如,將所有資源重新定位到/resources/**可以實(shí)現(xiàn)如下:
默認(rèn)靜態(tài)資源路徑
spring:
web:
resources:
static-locations:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
目錄結(jié)構(gòu)如下:

默認(rèn)訪問路徑:??http://localhost:8080/xxx.yy??
修改訪問路徑?
spring:
mvc:
static-path-pattern: /res/**
如上修改后訪問路徑:
?http://localhost:8080/res/xxx.yy??
注意:如果你使用的是舊版本Springboot,這里的靜態(tài)資源配置是spring.resources.static-locations
添加靜態(tài)資源路徑
spring:
web:
resources:
static-locations:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
- file:///D:/images/
上面的:file:///D:/images/
編程方式配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("file:///d:/images/") ;
registry.addResourceHandler("/h5/**").addResourceLocations("file:///d:/h5/") ;
}
}
上面配置了2個(gè)文件系統(tǒng)的資源目錄,分別以:/static/**,/h5/**路徑進(jìn)行訪問
訪問:
?http://localhost:8080/static/xxx.yy,http://localhost:8080/h5/xxx。??
WebJars靜態(tài)資源
除了前面提到的“標(biāo)準(zhǔn)”靜態(tài)資源位置之外,Webjars內(nèi)容還有一個(gè)特殊情況。任何路徑在/webjars/**中的資源都是從jar文件中提供的,前提是它們以webjars格式打包的。
如果你的應(yīng)用程序打包為jar,請不要使用src/main/webapp目錄。盡管這個(gè)目錄是一個(gè)常見的標(biāo)準(zhǔn),但它只適用于war打包,并且如果你生成一個(gè)jar,它會(huì)被大多數(shù)構(gòu)建工具默默地忽略。
Spring Boot還支持Spring MVC提供的高級資源處理功能,允許使用緩存破壞靜態(tài)資源或?yàn)閃ebjars使用版本不可知的URL等用例。
要為Webjars使用版本不可知的url,請?zhí)砑觲ebjars-locator-core依賴項(xiàng)。然后聲明你的webjar。以jQuery為例,添加"/webjars/jQuery/jQuery .min.js"會(huì)得到"
/webjars/jQuery/x.y.z/jQuery .min.js",其中x.y.z是webjar版本。
為了使用緩存破壞,下面的配置為所有靜態(tài)資源配置緩存破壞解決方案,有效地在url中添加內(nèi)容哈希,例如<link href="
/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>:?
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
靜態(tài)資源訪問原理
SpringMVC核心組件配置:?
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 注入當(dāng)前環(huán)境中所有的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) {
// 調(diào)用WebMvcConfigurerComposite#addResourceHandlers方法,該方法內(nèi)部
// 遍歷所有的WebMvcConfigurer分別調(diào)用addResourceHandlers方法
this.configurers.addResourceHandlers(registry);
}
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addResourceHandlers(registry);
}
}
}
Spring提供的一個(gè)WebMvcConfigurer實(shí)現(xiàn)?
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ...
// addResourceHandler注冊資源實(shí)例
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
// getStaticPathPattern獲取配置文件中spring.mvc.staticPathPattern屬性值
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
// getStaticLocations獲取配置文件中spring.web.resources.staticLocations屬性值
// 該方法調(diào)用后就會(huì)將資源訪問路徑與具體資源路徑進(jìn)行關(guān)聯(lián)
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
// 創(chuàng)建并獲取資源訪問模式的的實(shí)例
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
// 自定義配置
customizer.accept(registration);
// 緩存設(shè)置
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
customizeResourceHandlerRegistration(registration);
}
}
ResourceHandlerRegistry?
public class ResourceHandlerRegistry {
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
// 為每一種資源創(chuàng)建ResourceHandlerRegistration實(shí)例,添加到List集合中
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
this.registrations.add(registration);
return registration;
}
}
通過上面的源碼我們只看到收集容器中所有WebMvcConfigurer類型的Bean,然后分別調(diào)用重寫的addResourceHandlers方法接著為每一種資源訪問路徑/xxx創(chuàng)建對應(yīng)的ResourceHandlerRegistration實(shí)例,并且將這些實(shí)例添加到ResourceHandlerRegistry中。
這里有2個(gè)疑問:
- ResourceHandlerRegistry是如何創(chuàng)建的
- 當(dāng)訪問這些靜態(tài)資源時(shí)對應(yīng)的HandlerMapping及Adapter又是誰如何與上面的ResourceHandlerRegistration關(guān)聯(lián)的。
ResourceHandlerRegistry創(chuàng)建
上面的
DelegatingWebMvcConfiguration配置類繼承WebMvcConfigurationSupport,該父類中有如下方法:?
public class WebMvcConfigurationSupport {
// 該Bean是一個(gè)HandlerMapping(這是個(gè)接口),用來確定當(dāng)前請求對應(yīng)的處理器類
@Bean
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
// 這里創(chuàng)建了資源注冊器類
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
// 添加注冊靜態(tài)資源,該訪問正好被子類DelegatingWebMvcConfiguration重寫了
// 而 在上面源碼看到,子類就是遍歷了容器中所有的WebMvcConfigurer對應(yīng)的addResourceHandlers方法
// 到這里你就清楚了靜態(tài)資源的注冊入口,接下來就是這些靜態(tài)資源對應(yīng)是如何與HandlerMapping關(guān)聯(lián)的
addResourceHandlers(registry);
// 獲取HandlerMapping對象
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
// ...
return handlerMapping;
}
}
通過ResourceHandlerRegistry獲取HandlerMapping對象?
public class ResourceHandlerRegistry {
protected AbstractHandlerMapping getHandlerMapping() {
// 如果沒有配置靜態(tài)資源,那么就沒有必要注冊HandlerMapping了,直接返回null
if (this.registrations.isEmpty()) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
// 遍歷上面注冊的所有靜態(tài)資源對應(yīng)的ResourceHandlerRegistration
for (ResourceHandlerRegistration registration : this.registrations) {
// 將ResourceHandlerRegistration對象轉(zhuǎn)換為ResourceHttpRequestHandler對象
ResourceHttpRequestHandler handler = getRequestHandler(registration);
for (String pathPattern : registration.getPathPatterns()) {
// 以配置的訪問路徑為key,對應(yīng)的ResourceHttpRequestHandler為處理句柄
// 當(dāng)一個(gè)請求過來如果匹配了當(dāng)前的模式,那么就會(huì)用對應(yīng)的ResourceHttpRequestHandler對象進(jìn)行處理
urlMap.put(pathPattern, handler);
}
}
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
private ResourceHttpRequestHandler getRequestHandler(ResourceHandlerRegistration registration) {
// 獲取
ResourceHttpRequestHandler handler = registration.getRequestHandler();
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.applicationContext);
try {
// 執(zhí)行初始化
handler.afterPropertiesSet();
}
return handler;
}
}
public class ResourceHandlerRegistration {
protected ResourceHttpRequestHandler getRequestHandler() {
// 創(chuàng)建對象
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
// ...
// 設(shè)置路徑
handler.setLocationValues(this.locationValues);
handler.setLocations(this.locationsResources);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
// ... 這里緩存設(shè)置
return handler;
}
}
ResourceHttpRequestHandler對應(yīng)的HandlerAdapter對象?
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
// ResourceHttpRequestHandler實(shí)例HttpRequestHandler子類
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 直接調(diào)用ResourceHttpRequestHandler#handleRequest方法
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
}