简说Spring中的资源加载
声明: 本文若有 任何纰漏、错误,请不吝指正!谢谢!
问题描述
遇到一个关于资源加载的问题,因此简单的记录一下,对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
不为null
,SpringBootTest
实例化一个SpringBootMockServletContext
对象。
而正常情况下,在处理@PropertySource
时,还没能初始化一个ServletContext
,因为 @PropertySource
的处理是在BeanDefinitionRegistryPostProcessor
执行时处理的,早于SpringBoot
去初始化Servlet
容器。SpringBoot创建Servlet容器是在这里org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
,它的执行时机是晚于处理 BeanFactoryPostProcessor
的org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
,所以 正常运行应用,肯定只会创建一个ClassPathContextResource
资源对象,而配置文件在classpath
下是存在的,所以可以搜索到。
结论
结论就是不知道SpringBootTest
是故意为之呢还是出于什么别的考虑,也不知道除了加上classpath
前缀外是否有别的方式能解决这个问题。
不过现在看来,偷懒是不可能的呢了 ,老老实实的 把前缀classpath
给加上,就不会有问题了
简说Spring中的资源加载的更多相关文章
- Spring中的资源加载
大家也都知道JDK的类加载器:BootStrap ClassLoader.ExtenSion ClassLoader.Application ClassLoader:也使用了双亲委派模型,主要是为了防 ...
- 通过源码浅析Java中的资源加载
前提 最近在做一个基础组件项目刚好需要用到JDK中的资源加载,这里说到的资源包括类文件和其他静态资源,刚好需要重新补充一下类加载器和资源加载的相关知识,整理成一篇文章. 理解类的工作原理 这一节主要分 ...
- 关于web项目中静态资源加载不了的一些解决思路
问题的产生: <!--springMVC前端控制器加载--> <servlet> <servlet-name>springmvc</servlet-name& ...
- 【Spring】详解Spring中Bean的加载
之前写过bean的解析,这篇来讲讲bean的加载,加载要比bean的解析复杂些,该文之前在小编原文中有发表过,要看原文的可以直接点击原文查看,从之前的例子开始,Spring中加载一个bean的方式: ...
- 【死磕 Spring】----- IOC 之 Spring 统一资源加载策略
原文出自:http://cmsblogs.com 在学 Java SE 的时候我们学习了一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Reso ...
- spring资源访问接口和资源加载接口
spring 资源访问接口 JDK提供的资源访问类,如java.net.URL.File等,不能很好地满足各种资源的访问需求,比如缺少从类路径或者Web容器的上下文中获取资源的操作类. 鉴于此,spr ...
- 手撸Spring框架,设计与实现资源加载器,从Spring.xml解析和注册Bean对象
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你写的代码,能接的住产品加需求吗? 接,是能接的,接几次也行,哪怕就一个类一片的 i ...
- SpringBoot jar包中资源加载问题
在IDE下调试怎么也没有发现问题,但是部署到服务器上,提示找不到资源,找了半天资料总算是找到了原因: Jar包中的资源加载不能使用File方式,只能使用InputStream方式读取.知道原因就好解决 ...
- 【sping揭秘】6、IOC容器之统一资源加载策略
Spring中的resource 我们先看看类之间的关系 注意我们的application是间接继承了resourceloader的,也就是说我们的application其实就是一个resourcel ...
随机推荐
- db2 锁表
2019独角兽企业重金招聘Python工程师标准>>> 查询锁表情况 db2 => get snapshot for locks on databasename 可以看到什么表 ...
- Clickhouse 条形图📊函数展示
Clickhouse 条形图
- .user.ini 无法修改/删除 怎么办?
首先 了解chattr命令: Linux chattr命令用于改变文件属性. 这项指令可改变存放在ext2文件系统上的文件或目录属性,这些属性共有以下8种模式: a:让文件或目录仅供附加用途.b:不更 ...
- vue 遮罩层阻止默认滚动事件
vue中提供 @touchmove.prevent 方法可以完美解决这个问题. <div class="child" @touchmove.prevent ></ ...
- MySQL 增删改查(单表)
1.sql 新增语句 表中插入数据 insert into + 表名 values(字段1value1,字段2value1,字段3value1),(字段1value2,字段2value2,字段3val ...
- spring学习笔记(四)我对spring中bean生命周期的理解
我相信大部分同学对spring中bean的生命周期都不陌生,但是如果要详细的说出每一个步骤,可能能说出来的也不多,我之前也是这样,前几天调了一下spring的源码,看了一点书,突然一下明朗了,理解了s ...
- 带你看看Java的锁(二)-Semaphore
前言 简介 Semaphore 中文称信号量,它和ReentrantLock 有所区别,ReentrantLock是排他的,也就是只能允许一个线程拥有资源,Semaphore是共享的,它允许多个线程同 ...
- Flutter为什么使用Dart?
老孟导读:关于Flutter为什么使用Dart?这个话题,就像PHP是世界上最好的语言一样,争论从来没有停止过,有很多说法,比如: Google是为了推广Dart,Dart是亲儿子. Flutter团 ...
- search(12)- elastic4s-聚合=桶+度量
这篇我们介绍一下ES的聚合功能(aggregation).聚合是把索引数据可视化处理成可读有用数据的主要工具.聚合由bucket桶和metrics度量两部分组成. 所谓bucket就是SQL的GROU ...
- python入门及数字、字符串类型
目录 python开发框架 开发 1. 开发语言 2. 语言比对 3. python安装 4. Python开发IDE:pycharm ,eclipse python入门 1. 第一句Python 2 ...