(四) SpringBoot起飛之路-Web靜態資源處理

這是第四篇,關於如何處理第三方靜態資源以及自己的靜態資源的小結,其實如果僅僅想要知道將靜態資源放在哪裏,或者說怎麼直接用,其實幾句話就說完了,但是我在文中是循着源碼或者官網/Github,誘導到這幾個點,雖然算不得什麼源碼分析,不過起碼靜態資源處理的有關問題,起碼不算空口而說,算是簡單的引導吧

等到後面我再補充一些集成例如 MyBatis、Redis 等的內容,同樣有興趣的朋友可以去了解一下前三篇,你的贊就是對我最大的支持,感謝大家!

(一) SpringBoot起飛之路-HelloWorld

(二) SpringBoot起飛之路-入門原理分析

(三) SpringBoot起飛之路-YAML配置小結(入門必知必會)

說明:

  • 太忙啦,同時全放到一起,後來感覺移動端篇幅太長,閱讀體驗太差了,就打算分成幾篇來發
  • 才疏學淺,就會點淺薄的知識,大家權當一篇工具文來看啦,不喜勿憤哈 ~

(一) 靜態資源處理方式

前面的演示,我們只涉及到了直接返回一些數據,例如字符串等等,但是如果想要真正的去做一個完整的 Web 項目,沒有頁面以及諸多靜態資源(CSS、JS等)怎麼能行,按照以往 Spring 的開發來說,我們的 main 下會有一個 webapp文件夾,但是我們現在創建的 SpringBoot 項目卻不然,這是因爲 SpringBoot 對於靜態資源的放置,有自己的一套規定,下面來看一下吧

(1) 第一種映射規則

A:規則分析

首先來看一下 SpringMVC 關於 web 的配置,ctrl + n 查找一下 WebMvcAutoConfiguration 這個配置類,找到 addResourceHandlers,看到一項與資源配置有關,addResourceHandlers,的簡單看一下源碼,其實就大致能明白,訪此 /webjars/** 路徑下的內容,就會去 /META-INF/resources/webjars/ 下去找,

addResourceHandlers

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));

}

這是什麼意思呢?首先來看一下 webjars 的概念

B:Webjars

以前項目中,如果需要一些靜態資源,我們會直接引入文件到項目中,但是 Webjars 是通過 jar 包方式引入靜態資源的,來看一下:

去訪問一下官網:https://www.webjars.org

官網的說明已經告訴我們,WebJars 就是幫助我們把一些 web 庫例如 jQuery & Bootstrap 等打包到 jar 中,我們通過依賴就可以快速使用

WebJars are client-side web libraries (e.g. jQuery & Bootstrap) packaged into JAR (Java Archive) files.
Explicitly and easily manage the client-side dependencies in JVM-based web applications
Use JVM-based build tools (e.g. Maven, Gradle, sbt, …) to download your client-side dependencies

下面我們以 jquery 爲例使用一下,(爲了演示,隨便選個版本就行了):選擇 Mavan 的依賴

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

引入進 pom 後,我們回顧一下剛纔看得源碼

訪此 /webjars/** 路徑下的內容,就會去 /META-INF/resources/webjars/ 下去找

我們找到這個引入的 jquery 依賴,可以看到我們從 Webjars 網站引入的內容,都是符合 Springboot 默認格式要求的,所以下面直接訪問一下看一看

訪問的格式就是:http://localhost:8080/webjars/** 引入它會自動去 /META-INF/resources/webjars/ 下面找,所以直接從 jquery這個文件夾開始寫就可以了

所以訪問路徑爲:http://localhost:8080/webjars/jquery/3.5.1/jquery.js

(2) 第二種映射規則

A:規則分析

這些第三方的 web 庫的問題給出了一種方式,但是說了半天,還沒有說自己的頁面怎麼弄,如果想要使用自己的靜態資源又該怎麼辦呢?

繼續看 addResourceHandlers 的源碼:

//這裏是第一種
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));
    
}

剛纔我們分析完了上面的第一種,接着下面以幾乎相同的方式,又定義了一種映射的規則,第一種的方式爲:訪此 /webjars/** 路徑下的內容,就會去 /META-INF/resources/webjars/ 下去找

對照相同位置,我們去看第二種,也就是:訪問 staticPathPattern 此路徑下的內容,就會去 getResourceLocations(this.resourceProperties .getStaticLocations() 下去找

我們順着線索,繼續追過去

String staticPathPattern = this.mvcProperties.getStaticPathPattern();

最終找到了這麼一條,我們得到了 /** 這個路徑

private String staticPathPattern = "/**";

我們跳轉 this.resourceProperties.getStaticLocations()

得到這樣一個定義

private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

繼續跳轉,在ResourceProperties 中又找到了這樣一個定義

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",
    "classpath:/public/" 
};

到現在我們對於第二種方式的映射規則其實就清楚了

訪問 /** 此路徑下的內容,就會去 /META-INF/resources//resources//static//public/ 四者中去找

B:classpath 概念補充:

  • src 路徑下的文件編譯後會被放到 WEB-INF/classes 路徑下,所以默認classpath 就是指這裏

  • 用maven構建一個項目的時候,resources 目錄就是默認的 classpath

C:測試

下面測試一下,我們分別將自己定義的一個 js 文件放置於resources文件夾下的 resources、static、public 文件夾下(沒有就自己創建,static 是默認有的,現在的新版本直接放在外層的那個 resources 下是不可以的)

經過測試都是可以訪問到的

通過不同測試的比較,還可以得出一個結論:

訪問優先級:resources > static > public

D:自定義資源路徑

如果我們還想要自己指定靜態資源的存放路徑,一個簡單的配置就可以了,例如下面配置就是將路徑指定到 resources 的 ideal 和 jsjsjs 文件夾中

spring.resources.static-locations=classpath:/ideal/,classpath:/jsjsjs/

注意:一旦自定義配置了路徑,原來的自動配置就不會生效了

(二) 首頁文件處理

繼續看 WebMvcAutoConfiguration 這個配置類,找到了一個關於歡迎頁面相關的方法 welcomePageHandlerMapping,this.mvcProperties.getStaticPathPattern(),代表的是 /** 這個路徑繼續,看到其中又調用了 getWelcomePage()

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
 
 WelcomePageHandlerMapping welcomePageHandlerMapping = new   
 WelcomePageHandlerMapping(neTemplateAvailabilityProviders(applicationContext),   
 applicationContext,getWelcomePage(),
 this.mvcProperties.getStaticPathPattern());
 
 welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, 		mvcResourceUrlProvider));
 
 welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
 
 return welcomePageHandlerMapping;
}

跳轉過去,可以看到這麼兩個方法

locations 的定義是獲取到 resourceProperties.getStaticLocations(),這也就是剛纔我們所探索到的那幾個靜態資源文件夾,在 getIndexHtml 方法中,又進行了一個拼接,也就是找到 這幾個靜態資源文件夾下的 index.html

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");
}

結論:靜態資源文件夾下面的 index.html 就是默認的歡迎頁面

先不要急,我們把圖標文件的處理說完,一起來測試

(三) 圖標文件處理

首先要說明一下,在新一些的版本中例如,2.2.x 關於靜態資源的 favicon.ico 源碼是改動過的

(1) 2.2.x 前的版本

在此之前版本下默認是有一個默認的 favicon.ico 文件的,也就是咱們常說的綠葉子圖標,相關的代碼在 WebMvcAutoConfiguration 這個配置類中

@Configuration
		@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
		public static class FaviconConfiguration implements ResourceLoaderAware {

			private final ResourceProperties resourceProperties;

			private ResourceLoader resourceLoader;

			public FaviconConfiguration(ResourceProperties resourceProperties) {
				this.resourceProperties = resourceProperties;
			}

			@Override
			public void setResourceLoader(ResourceLoader resourceLoader) {
				this.resourceLoader = resourceLoader;
			}

			@Bean
			public SimpleUrlHandlerMapping faviconHandlerMapping() {
				SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
				mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
				mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
				return mapping;
			}

			@Bean
			public ResourceHttpRequestHandler faviconRequestHandler() {
				ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
				requestHandler.setLocations(resolveFaviconLocations());
				return requestHandler;
			}

			private List<Resource> resolveFaviconLocations() {
				String[] staticLocations = getResourceLocations(this.resourceProperties.getStaticLocations());
				List<Resource> locations = new ArrayList<>(staticLocations.length + 1);
				Arrays.stream(staticLocations).map(this.resourceLoader::getResource).forEach(locations::add);
				locations.add(new ClassPathResource("/"));
				return Collections.unmodifiableList(locations);
			}

		}

可以看到只要配置 spring.mvc.favicon.enabled 就可以選擇性的決定是否使用它默認的綠葉子圖標,例如配置爲 false 關閉默認圖標

application.properties

spring.mvc.favicon.enabled=false

如果想要使用自己定製的圖標,可以將文件命名爲 favicon.ico 然後放置於靜態資源文件夾下,就可以了

(2) 2.2.x 版本

關於圖標文件的處理,較新的版本做過一些改動,所以在 WebMvcAutoConfiguration 這個配置類中已經找不到關於 icon 相關的內容了,我們去 Github 看一下其改動

首先定位到這個類

然後跳轉到其 History,看到了 2019年8月21日和22日的兩個相關改動

  • 21日:先把靜態資源文件夾下的優先級提高到類路徑前

  • 22日:從類路徑根目錄刪除默認的favicon和對服務的支持,也就是說,不提供默認的ico文件了

(3) 開發者說明

我們可以繼續去看一下相關的 Issues ,看一下開發者爲什麼這麼做,下面我只截取了重要的三段

如果想看完整的可以訪問:https://github.com/spring-projects/spring-boot/issues/17925

vpavic commented on 21 Aug 2019

The default favicon served by Spring Boot could be classified as information leakage, in a similar manner like Server HTTP header (see #4730) and exception error attribute (see #7872) were.

I’d consider removing the default favicon as applications that don’t provide custom favicon will inevitably leak info about being powered by Spring Boot.

wilkinsona commented on 21 Aug 2019

This is quite tempting. While we have a configuration property (spring.mvc.favicon.enabled), it’s enabled by default. The docs also do not seem to mention that a default favicon will be served so some people may not be aware of the out-of-the-box behaviour.

If an application developer cares about the favicon they will provide their own. If they don’t care about it I doubt there’s much difference to them between serving a default icon and serving nothing.

wilkinsona commented on 21 Aug 2019

Another benefit of removing the default favicon is that we could then also remove the spring.mvc.favicon.enabled property. It’s a benefit as the property is confusing. Setting it to false does not, as the property’s name might suggest, disable serving of a favicon.ico completely. It only disables serving a favicon.ico from the root of the classpath. A favicon.ico that’s placed in one of the static resource locations will still be served.

wilkinsona commented on 21 Aug 2019

We’ve decided to remove the default favicon.ico file, the resource handler configuration, and the property. Users who were placing a custom favicon.ico in the root of the classpath should move it to a static resource location or configure their own resource mapping.

大家也可以自己翻譯,我簡單總結一下:

  • vpavic 認爲在 Spring Boot 提供默認的 Favicon 可能會導致網站信息泄露,如果用戶不進行自定義的圖標的設置,Spring Boot 就會用默認的綠葉子,那就會導致泄露網站的開發框架

  • wilkinsona 認爲正好順便把 spring.mvc.favicon.enabled 這個屬性也刪掉因爲設置此值爲 false 不是禁用圖標,而僅僅是禁用默認的綠葉子罷了,而且想要設置圖標的開發者,自然會關心,不在乎圖標設置的開發者,可能會有與那些設置了圖標的有一些較大的區別,或者出現一些不確定的因素

  • 因此,在Spring Boot2.2.x中,將默認的favicon.ico移除,同時也不再提供上述application.properties中的屬性配置

  • 所以想設置圖標只需要將圖標文件 favicon.ico 放在靜態資源文件夾下或者自己配置映射就可以了

(四) 執行測試

執行以下,可以看到主頁和圖標就都生效了

(五) 結尾

如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支持!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公衆號

在這裏的我們素不相識,卻都在爲了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章