声明: 本文若有 任何纰漏、错误,请不吝指正!谢谢!

问题描述

遇到一个关于资源加载的问题,因此简单的记录一下,对Spring资源加载也做一个记录。

问题起因是使用了@PropertySource来进行配置文件加载,配置路径时,没有使用关键字classpath来指明从classpath下面来查找配置文件。具体配置如下

 @PropertySource("config/application-download.yml", factory=YamlPropertySourceFactory)

这种方式在启动应用时,是没问题的,正常。但是在build时,跑单元测试,出了问题,说无法从ServletContext中找到/config/application-download.yml,然后加上了classpath,再跑了下就没错误了。

于是找到了处理@PropertySource的位置,跟踪代码找到了差异的原因。

源码解释

Spring对于资源,做了一个抽象,那就是Resource,资源的加载使用资源加载器来进行加载,ResourceLoader就是这样一个接口,用于定义对资源的加载行为的。

Spring中几乎所有的ApplicationContext都实现了它,应用十分的广泛。

除了各个ApplicationContext实现了它,它还有个可以独立使用的实现,也就是一会要提到的。

DefaultResourceLoader

这个实现类,是一个在框架外部独立使用版本,一般默认的都不简单 ,这个也不例外。

无论从哪里加载资源,使用DefaultResourceLoader来加载就行了

// org.springframework.core.io.DefaultResourceLoader#getResource
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null"); // 这个是提供的SPI使用的,没有采用子类实现的方式
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 如果以/开头,使用纯路径的方式,比如./config.properties
if (location.startsWith("/")) {
return getResourceByPath(location);
}
// 如果以classpath:开头,创建一个ClassPathResource资源对象
// 底层使用的是Class#getResourceAsStream,ClassLoader#getResourceAsStream
// 或者 ClassLoader#getSystemResourceAsStream,具体有机会再详细解释下这些
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
// 如果上面的判断不满足,直接使用java.net.URL来生成一个URL对象,
// 如果location为null,或者location没有指定协议,或者协议不能被识别
// 就会抛出异常
URL url = new URL(location);
//file:开头的 会使用创建一个FileUrlResource
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// 没有指定协议
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
// ClassPathResource的子类
return new ClassPathContextResource(path, getClassLoader());
}

这个类在Spring中被广泛使用,或者更具体的说,这个类的getResource方法,几乎遇到资源相关的加载动作都会调用到它。

各个ApplicationContext应该是加载资源最多的地方了,而AbstractApplicationContext正是继承了DefaultResourceLoader,才有了这中加载资源的能力。

不过DefaultResourceLoader也留给了子类的扩展点,主要是通过重写getResourceByPath这个方法。这里是继承的方式,也可以重写 getResource方法,这个方法在GenericApplicationContext中被重写了, 不过也没有做过多的操作,这里主要是可以在一个context中设置自己的资源加载器,一旦设置了,会将 ApplicationContext中所有的资源委托给它加载,一般不会有这个操作 。

遇到的问题 ,正是因为子类对 getResourceByPath的重写 ,导致了不一样的行为。

经过跟踪源码发现,正常启动应用的时候,实例化的是一个 AnnotationConfigServletWebServerApplicationContext实例 ,这个类继承自ServletWebServerApplicationContext,在ServletWebServerApplicationContext中重写了getResourceByPath

//  org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getResourceByPath
@Override
protected Resource getResourceByPath(String path) {
if (getServletContext() == null) {
// ServletContext为null,从classpath去查找
return new ClassPathContextResource(path, getClassLoader());
}
// 否则从ServletContext去查找
return new ServletContextResource(getServletContext(), path);
}

而通过 Debug发现,在使用SpirngBootTest执行单元测试,它实例化的是org.springframework.web.context.support.GenericWebApplicationContext

/**
* This implementation supports file paths beneath the root of the ServletContext.
* @see ServletContextResource
* 这里就是直接从ServletContext中去查找资源,一般就是webapp目录下。
*/
@Override
protected Resource getResourceByPath(String path) {
Assert.state(this.servletContext != null, "No ServletContext available");
return new ServletContextResource(this.servletContext, path);
}

并且这里ServletContext不为nullSpringBootTest实例化一个SpringBootMockServletContext对象。

而正常情况下,在处理@PropertySource时,还没能初始化一个ServletContext,因为 @PropertySource的处理是在BeanDefinitionRegistryPostProcessor执行时处理的,早于SpringBoot去初始化Servlet容器。SpringBoot创建Servlet容器是在这里org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh,它的执行时机是晚于处理 BeanFactoryPostProcessororg.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors,所以 正常运行应用,肯定只会创建一个ClassPathContextResource资源对象,而配置文件在classpath下是存在的,所以可以搜索到。

结论

结论就是不知道SpringBootTest是故意为之呢还是出于什么别的考虑,也不知道除了加上classpath前缀外是否有别的方式能解决这个问题。

不过现在看来,偷懒是不可能的呢了 ,老老实实的 把前缀classpath给加上,就不会有问题了

简说Spring中的资源加载的更多相关文章

  1. Spring中的资源加载

    大家也都知道JDK的类加载器:BootStrap ClassLoader.ExtenSion ClassLoader.Application ClassLoader:也使用了双亲委派模型,主要是为了防 ...

  2. 通过源码浅析Java中的资源加载

    前提 最近在做一个基础组件项目刚好需要用到JDK中的资源加载,这里说到的资源包括类文件和其他静态资源,刚好需要重新补充一下类加载器和资源加载的相关知识,整理成一篇文章. 理解类的工作原理 这一节主要分 ...

  3. 关于web项目中静态资源加载不了的一些解决思路

    问题的产生: <!--springMVC前端控制器加载--> <servlet> <servlet-name>springmvc</servlet-name& ...

  4. 【Spring】详解Spring中Bean的加载

    之前写过bean的解析,这篇来讲讲bean的加载,加载要比bean的解析复杂些,该文之前在小编原文中有发表过,要看原文的可以直接点击原文查看,从之前的例子开始,Spring中加载一个bean的方式: ...

  5. 【死磕 Spring】----- IOC 之 Spring 统一资源加载策略

    原文出自:http://cmsblogs.com 在学 Java SE 的时候我们学习了一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Reso ...

  6. spring资源访问接口和资源加载接口

    spring 资源访问接口 JDK提供的资源访问类,如java.net.URL.File等,不能很好地满足各种资源的访问需求,比如缺少从类路径或者Web容器的上下文中获取资源的操作类. 鉴于此,spr ...

  7. 手撸Spring框架,设计与实现资源加载器,从Spring.xml解析和注册Bean对象

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你写的代码,能接的住产品加需求吗? 接,是能接的,接几次也行,哪怕就一个类一片的 i ...

  8. SpringBoot jar包中资源加载问题

    在IDE下调试怎么也没有发现问题,但是部署到服务器上,提示找不到资源,找了半天资料总算是找到了原因: Jar包中的资源加载不能使用File方式,只能使用InputStream方式读取.知道原因就好解决 ...

  9. 【sping揭秘】6、IOC容器之统一资源加载策略

    Spring中的resource 我们先看看类之间的关系 注意我们的application是间接继承了resourceloader的,也就是说我们的application其实就是一个resourcel ...

随机推荐

  1. db2 锁表

    2019独角兽企业重金招聘Python工程师标准>>> 查询锁表情况 db2 => get snapshot for locks on databasename 可以看到什么表 ...

  2. Clickhouse 条形图📊函数展示

    Clickhouse 条形图

  3. .user.ini 无法修改/删除 怎么办?

    首先 了解chattr命令: Linux chattr命令用于改变文件属性. 这项指令可改变存放在ext2文件系统上的文件或目录属性,这些属性共有以下8种模式: a:让文件或目录仅供附加用途.b:不更 ...

  4. vue 遮罩层阻止默认滚动事件

    vue中提供 @touchmove.prevent 方法可以完美解决这个问题. <div class="child" @touchmove.prevent ></ ...

  5. MySQL 增删改查(单表)

    1.sql 新增语句 表中插入数据 insert into + 表名 values(字段1value1,字段2value1,字段3value1),(字段1value2,字段2value2,字段3val ...

  6. spring学习笔记(四)我对spring中bean生命周期的理解

    我相信大部分同学对spring中bean的生命周期都不陌生,但是如果要详细的说出每一个步骤,可能能说出来的也不多,我之前也是这样,前几天调了一下spring的源码,看了一点书,突然一下明朗了,理解了s ...

  7. 带你看看Java的锁(二)-Semaphore

    前言 简介 Semaphore 中文称信号量,它和ReentrantLock 有所区别,ReentrantLock是排他的,也就是只能允许一个线程拥有资源,Semaphore是共享的,它允许多个线程同 ...

  8. Flutter为什么使用Dart?

    老孟导读:关于Flutter为什么使用Dart?这个话题,就像PHP是世界上最好的语言一样,争论从来没有停止过,有很多说法,比如: Google是为了推广Dart,Dart是亲儿子. Flutter团 ...

  9. search(12)- elastic4s-聚合=桶+度量

    这篇我们介绍一下ES的聚合功能(aggregation).聚合是把索引数据可视化处理成可读有用数据的主要工具.聚合由bucket桶和metrics度量两部分组成. 所谓bucket就是SQL的GROU ...

  10. python入门及数字、字符串类型

    目录 python开发框架 开发 1. 开发语言 2. 语言比对 3. python安装 4. Python开发IDE:pycharm ,eclipse python入门 1. 第一句Python 2 ...