这是第四篇,关于如何处理第三方静态资源以及自己的静态资源的小结,其实如果仅仅想要知道将静态资源放在哪里,或者说怎么直接用,其实几句话就说完了,但是我在文中是循着源码或者官网/Github,诱导到这几个点,虽然算不得什么源码分析,不过起码静态资源处理的有关问题,起码不算空口而说,算是简单的引导吧
等到后面我再补充一些集成例如 MyBatis、Redis 等的内容,同样有兴趣的朋友可以去了解一下前三篇,你的赞就是对我最大的支持,感谢大家!
(一) SpringBoot起飞之路-HelloWorld
(二) SpringBoot起飞之路-入门原理分析
(三) SpringBoot起飞之路-YAML配置小结(入门必知必会)
前面的演示,我们只涉及到了直接返回一些数据,例如字符串等等,但是如果想要真正的去做一个完整的 Web 项目,没有页面以及诸多静态资源(CSS、JS等)怎么能行,按照以往 Spring 的开发来说,我们的 main 下会有一个 webapp文件夹,但是我们现在创建的 SpringBoot 项目却不然,这是因为 SpringBoot 对于静态资源的放置,有自己的一套规定,下面来看一下吧
首先来看一下 SpringMVC 关于 web 的配置, ctrl + n
查找一下 WebMvcAutoConfiguration 这个配置类,找到 addResourceHandlers,看到一项与资源配置有关, addResourceHandlers ,的简单看一下源码,其实就大致能明白,访此 /webjars/**
路径下的内容,就会去 /META-INF/resources/webjars/
下去找,
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 的概念
以前项目中,如果需要一些静态资源,我们会直接引入文件到项目中,但是 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
这些第三方的 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/
四者中去找
下面测试一下,我们分别将自己定义的一个 js 文件放置于resources文件夹下的 resources、static、public 文件夹下(没有就自己创建,static 是默认有的,现在的新版本直接放在外层的那个 resources 下是不可以的)
经过测试都是可以访问到的
通过不同测试的比较,还可以得出一个结论:
如果我们还想要自己指定静态资源的存放路径,一个简单的配置就可以了,例如下面配置就是将路径指定到 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"); }
先不要急,我们把图标文件的处理说完,一起来测试
首先要说明一下,在新一些的版本中例如,2.2.x 关于静态资源的 favicon.ico 源码是改动过的
在此之前版本下默认是有一个默认的 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 关闭默认图标
spring.mvc.favicon.enabled=false
如果想要使用自己定制的图标,可以将文件命名为 favicon.ico 然后放置于静态资源文件夹下,就可以了
关于图标文件的处理,较新的版本做过一些改动,所以在 WebMvcAutoConfiguration 这个配置类中已经找不到关于 icon 相关的内容了,我们去 Github 看一下其改动
首先定位到这个类
然后跳转到其 History,看到了 2019年8月21日和22日的两个相关改动
我们可以继续去看一下相关的 Issues ,看一下开发者为什么这么做,下面我只截取了重要的三段
如果想看完整的可以访问: https://github.com/spring-projects/spring-boot/issues/17925
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.
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.
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.
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.
大家也可以自己翻译,我简单总结一下:
执行以下,可以看到主页和图标就都生效了
如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
一个坚持推送原创开发技术文章的公众号:理想二旬不止