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

问题描述

遇到一个关于资源加载的问题,因此简单的记录一下,对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. 解决iframe跨域刷新的问题

    用iframe的location.reload(true); 方法来刷新外部URL会报 Blocked a frame with origin xxxx from accessing a cross- ...

  2. Pointers and Memory

    Stanford CS Education Library #102 一.Basic Pointers 指针主要有两个用途:使不同的代码段共享信息.方便链表(树)的处理. 指针示意图: derefer ...

  3. HTML--HTML入门篇(我想10分钟入门HTML,可以,交给我吧)

    我要正经的讲一节课,咳咳! HTML简介(废话) HTML称为超文本标记语言,是一种标识性的语言.它包括一系列标签.通过这些标签可以将网络上的文档格式统一,使分散的Internet资源连接为一个逻辑整 ...

  4. JavaSpring中级联查询

    一对一级联查询映射文件PersonMapper.xml代码: <?xml version="1.0" encoding="UTF-8"?> < ...

  5. 软件——IDEA 超实用使用技巧分享

    前言 工欲善其事 ​必先利其器 最近受部门的邀请,给入职新人统一培训IDEA,发现有很多新人虽然日常开发使用的是IDEA,但是还是很多好用的技巧没有用到,只是用到一些基本的功能,蛮浪费IDEA这个优秀 ...

  6. Java——多线程之对象及变量的并发访问

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  7. 第三章:Python高级编程-深入类和对象

    第三章:Python高级编程-深入类和对象 Python3高级核心技术97讲 笔记 3.1 鸭子类型和多态 """ 当看到一直鸟走起来像鸭子.游泳起来像鸭子.叫起来像鸭子 ...

  8. 用js写出一个漂亮的单选框选中效果

    一般的input框比较简单,我们可以用JavaScript配合css背景图片定位让我们模拟写出一个点击选中效果 首先需要有个图片素材,当页面加载的时候是背景图片定位到左图,当我们点击图片的时候,背景图 ...

  9. openshift 4.3 Istio的搭建(istio 系列一)

    openshift 4.3 Istio的搭建 本文档覆盖了官方文档的Setup的所有章节 目录 openshift 4.3 Istio的搭建 安装Istio openshift安装Istio 更新is ...

  10. STM32 CubeIDE快速创建工程(图文详解)

    使用STM32CubeIDE快速创建STM32的HAL库工程. 文章目录 1 STM32CubeIDE Home 2 生成工程 3 程序下载 1 STM32CubeIDE Home 进入到官网的下载界 ...