前言

很多的小伙伴刚刚接触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自定义资源映射路径的两种方式,相信小伙伴们会有一个比较深刻的印象了。

本文到这里就结束了,如果觉得内容对你有所帮助,那么欢迎持续关注后续文章。

往期文章推荐:

Windows下使用Nginx+Tomcat做负载均衡

聊聊分布式下的WebSocket解决方案

从SpringBoot源码看资源映射原理的更多相关文章

  1. 带着萌新看springboot源码12(启动原理 下)

    先继续接上一篇,那个启动原理还有一点没说完. 6. afterRefresh(context, applicationArguments); 看这个名字就知道,应该就是ioc容器刷新之后的一些操作了, ...

  2. Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

  3. SpringBoot源码学习系列之启动原理简介

    本博客通过debug方式简单跟一下Springboot application启动的源码,Springboot的启动源码是比较复杂的,本博客只是简单梳理一下源码,浅析其原理 为了方便跟源码,先找个Ap ...

  4. 带着萌新看springboot源码8(spring ioc源码 完)

    上一节说到实例化了所有的单实例Bean,后面还有一步遍历 12.完成容器刷新(finishRefresh();) 那个和生命周期有关的后置处理器类型是LifecycleProcessor:监听器原理我 ...

  5. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  6. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  7. 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局

    从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...

  8. SpringBoot源码学习系列之异常处理自动配置

    SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...

  9. 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...

随机推荐

  1. 结对项目:四则运算题目生成器(Java)

    目录 一.需求分析 二.开发计划 三.实现方案 3.1 项目结构 3.2 代码说明 3.2.1 出题功能代码 3.2.3 批卷功能代码 3.2.3 四则运算功能代码 四.效能分析 4.1 程序效能 4 ...

  2. 微众银行FATE联邦学习框架

    参考:https://github.com/webankfintech/fate https://www.fedai.org/#/ 一.Docker Standalone 安装 FATE $ sh b ...

  3. python数据处理工具 -- pandas(序列与数据框的构造)

    Pandas模块的核心操作对象就是对序列(Series)和数据框(Dataframe).序列可以理解为数据集中的一个字段,数据框是值包含至少两个字段(或序列) 的数据集. 构造序列 1.通过同质的列表 ...

  4. 区块链入门到实战(12)之区块链 – 默克尔树(Merkle Tree)

    目的:解决由于区块链过长,导致节点硬盘存不下的问题. 方法:只需保留交易的哈希值. 区块链作为分布式账本,原则上网络中的每个节点都应包含整个区块链中全部区块,随着区块链越来越长,节点的硬盘有可能放不下 ...

  5. Qt QString字符串分割、截取

    在做项目中不可避免的会使用到一串字符串中的一段字符,因此常常需要截取字符串. 有两种方式可以解决这个问题: 方法一:QString分割字符串: QString date=dateEdit.toStri ...

  6. java最简单的知识之创建一个简单的windows窗口,利用Frame类

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 QQ986945193 微博:http://weibo.com/mcxiaobing 首先给大家看一下 ...

  7. IOS 提审

    关于上架AppStore最后一步的“出口合规信息”.“内容版权”.“广告标识符”的选择 https://blog.csdn.net/ashimar_a/article/details/51745675

  8. Codeforces 1389 题解(A-E)

    AC代码 A. LCM Problem 若\(a < b\),则\(LCM(a,b)\)是\(a\)的整数倍且\(LCM(a,b) \ne a\),所以\(LCM(a,b) \ge 2a\),当 ...

  9. Google Kick Start 2020 Round B T1-3

    这场题目除了最后一题稍微难了点,其他都是1眼题. T1 Bike Tour 没啥好说的,一个循环解决. T2 Bus Routes 没啥好说的,从第\(n\)的车站开始贪心取最晚的. T3 Robot ...

  10. 创建node节点上kubeconfig文件

    #!/bin/bash#by love19791126 107420988@qq.com# 创建node节点上kubeconfig文件 在master节点部署#kubeconfig是用于Node节点上 ...