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

问题描述

遇到一个关于资源加载的问题,因此简单的记录一下,对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. 四、CentOS 6.5 上传和安装Nginx

    CentOS 6.5 上传和安装Nginx

  2. Centos7下查询jdk安装路径

    今天一个小实验需要安装jdk,用命令java -version查询了一下,原来Centos7自带OpenJDK的环境,但是需要手动配置/etc/profile文件,于是开始找java的安装路径.... ...

  3. CF思维联系– CodeForces -CodeForces - 992C Nastya and a Wardrobe(欧拉降幂+快速幂)

    Nastya received a gift on New Year - a magic wardrobe. It is magic because in the end of each month ...

  4. ACM--[kuangbin带你飞]--专题1-23

    专题一 简单搜索 POJ 1321 棋盘问题POJ 2251 Dungeon MasterPOJ 3278 Catch That CowPOJ 3279 FliptilePOJ 1426 Find T ...

  5. Linux下创建软、硬链接

    在linux系统中,内核为每一个新创建的文件分配一个Inode(索引节点),每个文件都有唯一的inode号.文件属性保存在索引节点里,在访问文件时,索引节点被复制到内存,从而实现文件的快速访问. 链接 ...

  6. CF #632 (Div. 2) 对应题号CF1333

    1333A Little Artem 在一个\(n\)行\(m\)列的格子上染色,每个格子能染黑白两种 构造一种方案,使得四个方向有至少一个白色格子的黑色格子的数量,比四个方向有至少一个黑色格子的白色 ...

  7. 解决python语言的工具pycharm以及Windows电脑安装pygame模块的问题

    人生苦短,我用python,python作为一门当今时代潮流性的语言,已经成为大多数的年轻程序猿们向往的目标,python中有许多的库, 其中有一个pygame库是作为开发2D游戏必不可少的开发库,是 ...

  8. 《Docker从入门到跑路》之多阶段构建

    多阶段构建就是在一个Dokcerfile中定义多个FROM,每个FROM都可以使用不同的基础镜像,并表示开始一个新的构建阶段,我们可以很方便的将一个阶段的文件复制到另外一个阶段中,在最终的阶段保存你需 ...

  9. xenomai内核解析之xenomai的组成结构

    @ 目录 一.xenomai 3 二.xenomai3 结构 这是第二篇笔记. 一.xenomai 3 从xenomai3开始支持两种方式构建linux实时系统,分别是cobalt 和 mercury ...

  10. Navicat12.1系列安装,破解以及破解navicat报错的解决方案

    由于上课的需要,我们必须自己下载并安装 Navicat Premium 12,虽然安装过程很简单,但是安装后的navicat只能试用,并没有永久激活,然而我还想永久使用,所以就各种百度,因为不断地遇到 ...