一、前言
springboot配置靜態資源方式是多種多樣,接下來我會介紹其中幾種方式,并解析一下其中的原理。
二、使用properties屬性進行配置
應該說?spring.mvc.static-path-pattern 和?spring.resources.static-locations這兩屬性是成對使用的,如果不明白其中的原理,總會出現資源404的情況。首先收一下spring.mvc.static-path-pattern代表的是一個Ant Path路徑,例如resources/**,表示當你的路徑中存在resources/**的時候才會處理請求。比如我們訪問“http://localhost:8080/resources/xxx.js”時,很顯然,springboot邏輯中會根據模式匹配對url進行匹配,匹配命中后,是如何再定位到具體的資源的呢?這時候spring.resources.static-locations的配置就起作用了。
忘記說了,在springboot中spring.mvc.static-path-pattern的默認值是/**,spring.resources.static-locations的默認值是classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources,servlet context:/,springboot中相關的ResourceHttpRequestHandler就會去spring.resources.static-locations配置的所有路徑中尋找資源文件。
所以我之前才說spring.mvc.static-path-pattern 和?spring.resources.static-locations這兩屬性是成對使用的。
三、springboot中默認對靜態資源的處理
調試過程中,通過查看?org.springframework.web.servlet.DispatcherServlet中的handlerMappings變量,我們發現有一個很顯眼的?resourceHandlerMapping?,這個是springboot為我們提供的一個默認的靜態資源handler,通過全文搜索發現出現在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport這個類中,也就是這個類包含了@EnableWebMvc注解中的大多數功能,更多的擴展功能請參考org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration。
resourceHandlerMapping?的定義如下。
/*** Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped* resource handlers. To configure resource handling, override* {@link #addResourceHandlers}.*/ @Bean public HandlerMapping resourceHandlerMapping() {ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,this.servletContext, mvcContentNegotiationManager());addResourceHandlers(registry);AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();if (handlerMapping != null) {handlerMapping.setPathMatcher(mvcPathMatcher());handlerMapping.setUrlPathHelper(mvcUrlPathHelper());handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));handlerMapping.setCorsConfigurations(getCorsConfigurations());}else {handlerMapping = new EmptyHandlerMapping();}return handlerMapping; }
請大家先記住ResourceHandlerRegistry這個類。
? ?首先看一下addResourceHandlers(registry);這個方法,父類DelegatingWebMvcConfiguration做了實現,如下。
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) {this.configurers.addResourceHandlers(registry); }
其中WebMvcConfigurerComposite是操作了WebMvcConfigurer類型的對象的集合。在org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration這個springmvc的自動配置類中,有一個WebMvcConfigurer的實現類,如下。
// Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when not // on the classpath @Configuration @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {...@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");return;}Integer cachePeriod = this.resourceProperties.getCachePeriod();if (!registry.hasMappingForPattern("/webjars/**")) {customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(cachePeriod));} String staticPathPattern = this.mvcProperties.getStaticPathPattern();if (!registry.hasMappingForPattern(staticPathPattern)) {customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern).addResourceLocations(this.resourceProperties.getStaticLocations()).setCachePeriod(cachePeriod));}}... }
上面的addResourceHandlers方法中,增加了默認的mapping pattern =?/webjars/** ,默認的resource location是classpath:/META-INF/resources/webjars/。正是這里的配置,我們在集成swagger的時候,就可以正常訪問到swagger webjars中的js文件了。其中紅色的代碼部分就是用戶可以自定義的默認靜態資源訪問方式,并通過ResourceHandlerRegistry對象進行注冊。接著看一下mvcProperties和resourceProperties對應的類吧。
@ConfigurationProperties("spring.mvc") public class WebMvcProperties {.../*** Path pattern used for static resources.*/private String staticPathPattern = "/**";... }
WebMvcProperties類中的staticPathPattern field 對應了spring.mvc.static-path-pattern這個屬性,可以看到默認值是 "/**"。
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) public class ResourceProperties implements ResourceLoaderAware {.....private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/" };private static final String[] RESOURCE_LOCATIONS;static {RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length+ SERVLET_RESOURCE_LOCATIONS.length];System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,SERVLET_RESOURCE_LOCATIONS.length);System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);}private String[] staticLocations = RESOURCE_LOCATIONS;...... }
ResourceProperties中staticLocations field 對應了?spring.resources.static-locations 這個屬性。可以看到默認值是classpath:[/META-INF/resources/, /resources/, /static/, /public/], servlet context:/
四、靜態資源的Bean配置
在了解了springboot默認資源的配置的原理(即?spring.mvc.static-path-pattern 和?spring.resources.static-locations),我們可以增加一個WebMvcConfigurer類型的bean,來添加靜態資源的訪問方式,還記得上面說的“請記住ResourceHandlerRegistry這個類“,下面就用到了哦。
@Configuration public class ResourceWebMvcConfigurer extends WebMvcConfigurerAdapter {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/public-resources/").setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());} }
那么當訪問路徑中包含"resources/**"的時候,resource handler就會去classpath:/public-resources目錄下尋找了。
五、靜態資源的查找
參考?org.springframework.web.servlet.resource.ResourceHttpRequestHandler,ResourceHttpRequestHandler中通過org.springframework.web.servlet.resource.PathResourceResolver進行查找。
舉個例子,下圖是springboot打包之后的目錄結構,現在想要通過url訪問application.properties文件,springboot默認的靜態文件配置可以嗎?當然需要用事實來說話了。
?
? 我們已經知道,默認的resource locations中有個 servlet-context:/,訪問你的url是http://localhost:8080/工程名/application.properties,調試一下PathResourceResolver,結果如下。
發現servlet-context的根路徑如上圖所示,查看一下這個路徑對應的目錄,發現什么都沒有,所以很顯然無法找到我們要找的文件了。畢竟一般使用springboot都是jar項目,servlet-context path下沒有用戶自定義的資源。
?六、其他方式
在Servlet3協議規范中,包含在JAR文件/META-INFO/resources/路徑下的資源可以直接訪問了。如果將springboot項目打包成war包,可以配置一個默認的servlet。在WebMvcConfigurationSupport中已經定義好了,不過默認是一個EmptyHandlerMapping。
/*** Return a handler mapping ordered at Integer.MAX_VALUE with a mapped* default servlet handler. To configure "default" Servlet handling,* override {@link #configureDefaultServletHandling}.*/ @Bean public HandlerMapping defaultServletHandlerMapping() {DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext);configureDefaultServletHandling(configurer);AbstractHandlerMapping handlerMapping = configurer.getHandlerMapping();handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();return handlerMapping; }
可以通過自定義一個WebMvcConfigurer類型的bean,改寫configureDefaultServletHandling 方法,如下。
@Configuration public class MyWebConfigurer extends WebMvcConfigurerAdapter {@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();} }
這樣就設置了一個默認的servlet,在加載靜態資源的時候就會按照servelt方式去加載了。
?
就先分享這么多了,更多分享請關注我們的技術公眾號吧!!!