简说Spring中的资源加载

简介:

简说Spring中的资源加载

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

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

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

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

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

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

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

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

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

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

Copy
// 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

Copy
// 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

Copy
/**

  • 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给加上,就不会有问题了

作者: 早知今日

出处:https://www.cnblogs.com/heartlake/p/12974265.html

相关文章
|
6月前
|
Java 数据库 Spring
【Spring】资源操作管理:Resource、ResourceLoader、ResourceLoaderAware;
【Spring】资源操作管理:Resource、ResourceLoader、ResourceLoaderAware;
132 1
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
212 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
2月前
|
消息中间件 NoSQL 安全
(转)Spring Boot加载 不同位置的 application.properties配置文件顺序规则
这篇文章介绍了Spring Boot加载配置文件的顺序规则,包括不同位置的application.properties文件的加载优先级,以及如何通过命令行参数或环境变量来指定配置文件的名称和位置。
|
3月前
|
Java Spring
Spring boot +Thymeleaf 本地图片加载失败(图片路径)的问题及解决方法
这篇文章详细讲解了在Spring Boot应用程序中本地图片无法加载的问题原因,并提供了两个示例来说明如何通过使用正确的相对路径或Thymeleaf语法来解决图片路径问题。
|
3月前
|
前端开发 JavaScript Java
Spring boot 本地图片不能加载(图片路径)的问题及解决方法
这篇文章讨论了Spring Boot应用程序中本地图片无法加载的问题,通常由图片路径不正确引起,并提供了使用正确的相对路径和Thymeleaf语法来解决这一问题的两种方法。
|
3月前
|
前端开发 JavaScript Java
Spring Boot应用中的资源分离与高效打包实践
通过实施资源分离和高效打包策略,不仅可以提升Spring Boot应用的开发和部署效率,还能显著提高用户体验。在实际项目中,根据项目的实际情况和团队的技术栈选择合适的工具和方案是关键。希望本文能为读者在Spring Boot项目中实现资源分离和高效打包提供一些有价值的参考。
|
5月前
|
运维 Java 测试技术
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求
47 3
|
6月前
|
Java 容器 Spring
Spring的加载配置文件、容器和获取bean的方式
Spring的加载配置文件、容器和获取bean的方式
53 3
Spring的加载配置文件、容器和获取bean的方式
|
4月前
|
前端开发 JavaScript Java
使用Spring Boot实现跨域资源共享(CORS)
使用Spring Boot实现跨域资源共享(CORS)