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

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