前言
很多的小夥伴剛剛接觸SpringBoot的時候,可能會遇到載入不到靜態資源的情況。
比如html沒有樣式,圖片無法載入等等。
今天王子就與大家一起看看SpringBoot中關於資源對映部分的主要原始碼實現。
建議環境允許的情況下,小夥伴們自己使用idea建立一個springBoot專案,跟著文章和王子一起看一看原始碼,更容易理解。
SSM中的資源對映
在談SpringBoot之前,我們先回顧一下SSM中關於資源配置是如何實現的。
在SSM環境下,一般我們可以通過<mvc:resources />節點來配置不攔截靜態資源,就像下邊這樣:
<mvc:resources mapping="/js/**" location="/js/" /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/img/**" location="/img/" />
這裡的/**表示的是可以匹配任意層級的路徑,屬於ant風格表示式,感興趣的小夥伴自行百度瞭解即可,所以也可以寫成下面這樣
<mvc:resources mapping="/**" location="/" />
上邊的這種配置方式是屬於XML配置的方式,SpringMVC的配置方式除了XML配置,也是可以通過java程式碼配置的,只需要我們自己定義一個類,來繼WebMvcConfigurationSupport這個類即可,我們看一下WebMvcConfigurationSupport的原始碼部分:
/** * Override this method to add resource handlers for serving static resources. * @see ResourceHandlerRegistry */ protected void addResourceHandlers(ResourceHandlerRegistry registry) { }
上邊的註釋寫的很清楚,重寫這個方法來增加一個靜態資源的對映。那麼具體怎麼寫呢?
我們再按照註釋看一下ResourceHandlerRegistry的原始碼,重點看一下對這個類的註釋,如下。
/* <p>To create a resource handler, use {@link #addResourceHandler(String...)} providing the URL path patterns / * for which the handler should be invoked to serve static resources (e.g. {@code "/resources/**"}).
大概意思就是為了建立資源的處理器,要呼叫addResourceHandler方法來提供url的表示式,這個方法是為了服務靜態資源的(ps:王子的英語水平也一般,瞭解大意即可)
然後我們去看addResourceHandler方法:
/** * Add a resource handler for serving static resources based on the specified URL path patterns. * The handler will be invoked for every incoming request that matches to one of the specified * path patterns. * <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\\w+\\.css}"} are allowed. * See {@link org.springframework.util.AntPathMatcher} for more details on the syntax. * @return a {@link ResourceHandlerRegistration} to use to further configure the * registered resource handler */ public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) { ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns); this.registrations.add(registration); return registration; }
我們看到這個方法執行後返回了一個新的類ResourceHandlerRegistration
那我們再來看一下這個類中的核心方法
/** * Add one or more resource locations from which to serve static content. * Each location must point to a valid directory. Multiple locations may * be specified as a comma-separated list, and the locations will be checked * for a given resource in the order specified. * <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} * allows resources to be served both from the web application root and * from any JAR on the classpath that contains a * {@code /META-INF/public-web-resources/} directory, with resources in the * web application root taking precedence. * <p>For {@link org.springframework.core.io.UrlResource URL-based resources} * (e.g. files, HTTP URLs, etc) this method supports a special prefix to * indicate the charset associated with the URL so that relative paths * appended to it can be encoded correctly, e.g. * {@code [charset=Windows-31J]https://example.org/path}. * @return the same {@link ResourceHandlerRegistration} instance, for * chained method invocation */ public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) { this.locationValues.addAll(Arrays.asList(resourceLocations)); return this; }
上邊註釋的大意就是增加一個或者多個靜態資源路徑,並舉了一些例子。原始碼我們就看到這裡。
所以我們可以像這樣實現資源的對映:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; /** * @author liumeng * @Date: 2020/9/25 09:20 * @Description: */ @Configuration public class SpringMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("/"); } }
關於SpringMVC中的資源對映部分就介紹到這,那麼我們繼續來看SpringBoot的資源對映吧。
SpringBoot的資源對映
其實SpringBoot的資源對映也是一脈相承的,當我們初始化一個SpringBoot專案後,靜態資源會預設存在resource/static目錄中,那麼SpringBoot的底層是怎麼實現的呢,接下來我們就去原始碼裡探索一下。
SpringBoot的原始碼在WebMvcAutoConfiguration這個類中,我們發現了熟悉的程式碼:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
這裡面我們重點看下邊這部分
String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); }
我們看一下getStaticPathPattern()方法的實現,發現獲得的就是
/** * Path pattern used for static resources. */ private String staticPathPattern = "/**";
這個屬性的值,預設是/**。
然後我們再看this.resourceProperties.getStaticLocations()方法,發現獲得的是
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
而CLASSPATH_RESOURCE_LOCATIONS是一個常量,值如下:
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
在這裡我們看到了四個值,static就是其中一個,到這裡我們就明白了SpringBoot的靜態資源為什麼會存在resource/static這個目錄下,而且放在以上4個目錄中是都可以讀取到的。
實際上SpringBoot預設的靜態資源是5個,我們再來看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; }
可以看到,除了我們之前看到的4個路徑,這個方法裡還新增了一個SERVLET_LOCATIONS的路徑,點進去看一下
private static final String[] SERVLET_LOCATIONS = { "/" };
發現就是一個"/"。
所以實際上SpringBoot的預設靜態資源路徑有5個:
"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/",“/”
自定義配置
好了,到現在我們已經知道了SpringBoot的預設資源對映來源,那麼我們如何配置自定義的資源對映路徑呢?
其實我們可以直接通過application.properties配置,如下:
spring.resources.static-locations=classpath:/
spring.mvc.static-path-pattern=/**
那麼為什麼這樣配置就可以了呢,是因為相應的類上使用了下面的註解:
@ConfigurationProperties(prefix = "spring.mvc")
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
本文就不對這個註解再做深入研究了。感興趣的小夥伴可以持續關注我們的後續文章。
除了通過配置檔案自定義,還可以通過java程式碼進行配置,這種方式和我們上文說到的SpringMvc方式比較類似,只不過我們這次是實現的WebMvcConfigurer這個介面
實現方式如下:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class ResourcesConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler( "/**").addResourceLocations("classpath:/aaa"); } }
有了上邊文章的鋪墊,相信大家對於這段程式碼應該秒懂了吧。
到這裡,小夥伴們是否會有個疑問,WebMvcConfigurer和WebMvcConfigurationSupport有什麼關係呢?
這個問題本文就不再探索了,留給大家自行探索。
總結
好了,今天王子和大家一起從SpringMVC的原始碼開始探索,引出了SpringBoot的資源對映配置原理。
又介紹了SpringBoot自定義資源對映路徑的兩種方式,相信小夥伴們會有一個比較深刻的印象了。
本文到這裡就結束了,如果覺得內容對你有所幫助,那麼歡迎持續關注後續文章。
往期文章推薦: