Spring源码学习-容器初始化之FileSystemXmlApplicationContext(二)路径格式及解析方式(上)

简介:

  了解完了构造函数,我们回到上节《Spring源码学习-容器初始化之FileSystemXmlApplicationContext(一)构造函数》留下的思考的问题:

  1. 支持路径格式的研究。(绝对?相对?通配符?classpath格式又如何?)
  2. 配合placeholder使用的路径问题研究。 
  3. 路径如何解析?

下面,我们就来一一验证和解答。

先放出本次测试用的配置文件(app-context和test.properties):

 
 
  1. <bean id="placeHolderConfig" 
  2.  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
  3.  <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />  
  4.  <property name="locations"> 
  5.  <list> 
  6.  <value>classpath*:spring/test.properties</value> 
  7.  </list> 
  8.  </property> 
  9.  </bean> 
  10.  <bean id="veryCommonBean" class="kubi.coder.bean.VeryCommonBean"> 
  11.  <property name="name" value="${test.name}"></property> 
  12.  </bean> 


 
 
  1. test.name=verycommonbean-name 

首先想到的自然是最普通的绝对路径


 
 
  1. /** 
  2.   * 测试通过普通的绝对路径: 
  3.   * <p>D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p> 
  4.   * 读取配置文件 
  5.   *  
  6.   * @author lihzh 
  7.   * @date 2012-5-5 上午10:53:53 
  8.   */ 
  9.  @Test 
  10.  public void testPlainAbsolutePath() { 
  11.  String path = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml"
  12.  ApplicationContext appContext = new FileSystemXmlApplicationContext(path); 
  13.  assertNotNull(appContext); 
  14.  VeryCommonBean bean = appContext.getBean(VeryCommonBean.class); 
  15.  assertNotNull(bean); 
  16.  assertEquals("verycommonbean-name", bean.getName()); 
  17.  } 

测试通过,我们来看下Spring是怎么找到该文件的。之前已经说过refresh这个函数,是Spring生命周期的开始,我们就以它为入口,顺藤摸瓜,时序图如下:

最终,我们找到解析路径的关键方法,PathMatchingResourcePatternResolver的getResources方法和DefaultResourceLoader中的getResource方法:


 
 
  1. public Resource[] getResources(String locationPattern) throws IOException { 
  2.  Assert.notNull(locationPattern, "Location pattern must not be null"); 
  3.  if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { 
  4.  // a class path resource (multiple resources for same name possible) 
  5.  if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 
  6.  // a class path resource pattern 
  7.  return findPathMatchingResources(locationPattern); 
  8.  } 
  9.  else { 
  10.  // all class path resources with the given name 
  11.  return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); 
  12.  } 
  13.  } 
  14.  else { 
  15.  // Only look for a pattern after a prefix here 
  16.  // (to not get fooled by a pattern symbol in a strange prefix). 
  17.  int prefixEnd = locationPattern.indexOf(":") + 1
  18.  if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { 
  19.  // a file pattern 
  20.  return findPathMatchingResources(locationPattern); 
  21.  } 
  22.  else { 
  23.  // a single resource with the given name 
  24.  return new Resource[] {getResourceLoader().getResource(locationPattern)}; 
  25.  } 
  26.  } 
  27.  } 


 
 
  1. public Resource getResource(String location) { 
  2.  Assert.notNull(location, "Location must not be null"); 
  3.  if (location.startsWith(CLASSPATH_URL_PREFIX)) { 
  4.  return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); 
  5.  } 
  6.  else { 
  7.  try { 
  8.  // Try to parse the location as a URL... 
  9.  URL url = new URL(location); 
  10.  return new UrlResource(url); 
  11.  } 
  12.  catch (MalformedURLException ex) { 
  13.  // No URL -> resolve as resource path. 
  14.  return getResourceByPath(location); 
  15.  } 
  16.  } 
  17.  } 

 其中常量

CLASSPATH_ALL_URL_PREFIX = "classpath*:";
CLASSPATH_URL_PREFIX = "classpath:";
我们输入的路径是绝对路径:"D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml"。不是以classpath*开头的,所以会落入else之中。在else中:getPathMatcher().isPattern(),实际是调用AntPathMatcher中的isPattern()方法:

 
 
  1. public boolean isPattern(String path) { 
  2.         return (path.indexOf('*') != -1 || path.indexOf('?') != -1); 
  3.     } 
是用来判断":"以后的路径中是否包含通配符“*”或者 "?"。
我们的路径显然也不包含,所以最终会直接走入getResource方法。
仍然,路径既不是以classpath开头的,也不是URL格式的路径,所以最终会落入 getResourceByPath(location)这个分支,而我们之前介绍过,这个方法恰好是在FileSystemXmlApplicationContext这个类中复写过的:

  
  
  1. protected Resource getResourceByPath(String path) { 
  2.  if (path != null && path.startsWith("/")) { 
  3.  path = path.substring(1); 
  4.  } 
  5.  return new FileSystemResource(path); 
  6.  } 

 我们给的路径不是以"/"开头,所以直接构造了一个FileSystemResource:


 
 
  1. public FileSystemResource(String path) { 
  2.  Assert.notNull(path, "Path must not be null"); 
  3.  this.file = new File(path); 
  4.  this.path = StringUtils.cleanPath(path); 
  5.  } 

 

即用路径直接构造了一个File。这里StringUtil.cleanPath方法:
主要是将传入的路径规范化,比如将windows的路径分隔符“\\”替换为标准的“/“,如果路径中含有.(当前文件夹),或者..(上层文件夹),则计算出其真实路径。而File本身是支持这样的路径的,也就是说,spring可以支持这样的路径。出于好奇,我们也针对这个方法测试如下:

 
 
  1. /** 
  2.   * 测试通过含有.或者..的绝对路径 
  3.   * <p>D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml</p> 
  4.   * 读取配置文件 
  5.   *  
  6.   * @author lihzh 
  7.   * @date 2012-5-5 上午10:53:53 
  8.   */ 
  9.  @Test 
  10.  public void testContainDotAbsolutePath() { 
  11.  String path = "D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml"
  12.  ApplicationContext appContext = new FileSystemXmlApplicationContext(path); 
  13.  assertNotNull(appContext); 
  14.  VeryCommonBean bean = appContext.getBean(VeryCommonBean.class); 
  15.  assertNotNull(bean); 
  16.  assertEquals("verycommonbean-name", bean.getName()); 
  17.  } 

容器可以正常初始化。路径计算正确。
 
补充说明:Spring最终读取配置文件,是通过InputStream加载的,Spring中的各种Resource的最上层接口InputStreamResource中定义了唯一的一个方法getInputStream。也就是说,只要保证各Resource的实现类的getInputStream方法能够正常获取流,Spring容器即可解析初始化。对于FileSystemResource而已,其实现如下:

 
 
  1. /** 
  2.   * This implementation opens a FileInputStream for the underlying file. 
  3.   * @see java.io.FileInputStream 
  4.   */ 
  5.  public InputStream getInputStream() throws IOException { 
  6.  return new FileInputStream(this.file); 
  7.  } 

所以,我们说,此时只有是File正常支持的格式,Spring才能正常初始化。
 
继续回到前面的话题。我们目前只验证else分支中的catch分支。根据代码分析,即使是FileSystemXmlApplicationContext也可以支持Classpath格式的路径和URL格式的路径的。验证如下:

 
 
  1. /** 
  2.   * 测试通过含有.或者..的绝对路径 
  3.   * <p>file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p> 
  4.   * 读取配置文件 
  5.   *  
  6.   * @author lihzh 
  7.   * @date 2012-5-5 上午10:53:53 
  8.   */ 
  9.  @Test 
  10.  public void testURLAbsolutePath() { 
  11.  String path = "file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml"
  12.  ApplicationContext appContext = new FileSystemXmlApplicationContext(path); 
  13.  assertNotNull(appContext); 
  14.  VeryCommonBean bean = appContext.getBean(VeryCommonBean.class); 
  15.  assertNotNull(bean); 
  16.  assertEquals("verycommonbean-name", bean.getName()); 
  17.  } 
  18.   
  19.  /** 
  20.   * 测试通过Classpath类型的路径 
  21.   * <p>classpath:spring/app-context.xml</p> 
  22.   * 通过读取配置文件 
  23.   *  
  24.   * @author lihzh 
  25.   * @date 2012-5-5 上午10:53:53 
  26.   */ 
  27.  @Test 
  28.  public void testClassPathStylePath() { 
  29.  String path = "classpath:spring/app-context.xml"
  30.  ApplicationContext appContext = new FileSystemXmlApplicationContext(path); 
  31.  assertNotNull(appContext); 
  32.  VeryCommonBean bean = appContext.getBean(VeryCommonBean.class); 
  33.  assertNotNull(bean); 
  34.  assertEquals("verycommonbean-name", bean.getName()); 
  35.  } 
 

验证通过,并且通过debug确认,确实走入了相应的分支,分别构造了UrlResource和ClassPathResource实例。所以,之后Spring会分别调用这个两个Resource中的getInputStream方法获取流,解析配置文件。附上这两个类中的getInputStream方法,有兴趣的可以继续研究:


 
 
  1.        /** 
  2.  * This implementation opens an InputStream for the given URL. 
  3.  * It sets the "UseCaches" flag to <code>false</code>, 
  4.  * mainly to avoid jar file locking on Windows. 
  5.  * @see java.net.URL#openConnection() 
  6.  * @see java.net.URLConnection#setUseCaches(boolean) 
  7.  * @see java.net.URLConnection#getInputStream() 
  8.  */ 
  9. public InputStream getInputStream() throws IOException { 
  10. URLConnection con = this.url.openConnection(); 
  11. ResourceUtils.useCachesIfNecessary(con); 
  12. try { 
  13. return con.getInputStream(); 
  14. catch (IOException ex) { 
  15. // Close the HTTP connection (if applicable). 
  16. if (con instanceof HttpURLConnection) { 
  17. ((HttpURLConnection) con).disconnect(); 
  18. throw ex; 
  19.  
  20.        /** 
  21.  * This implementation opens an InputStream for the given class path resource. 
  22.  * @see java.lang.ClassLoader#getResourceAsStream(String) 
  23.  * @see java.lang.Class#getResourceAsStream(String) 
  24.  */ 
  25. public InputStream getInputStream() throws IOException { 
  26. InputStream is; 
  27. if (this.clazz != null) { 
  28. is = this.clazz.getResourceAsStream(this.path); 
  29. else { 
  30. is = this.classLoader.getResourceAsStream(this.path); 
  31. if (is == null) { 
  32. throw new FileNotFoundException( 
  33. getDescription() + " cannot be opened because it does not exist"); 
  34. return is; 

上述两个实现所属的类,我想应该一目了然吧~~
 
至此,我们算是分析验证通过了一个小分支下的支持的路径的情况,其实,这只是这些都是最简单直接的。回想刚才的分析, 如果路径包含通配符(?,*)spring是怎么处理的?如果是以classpath*开头的又是如何呢??鉴于害怕文章过长,我们下回分解…………o(∩_∩)o 



     本文转自mushiqianmeng 51CTO博客,原文链接:http://blog.51cto.com/mushiqianmeng/860258,如需转载请自行联系原作者



相关文章
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
588 0
|
JSON 前端开发 搜索推荐
关于商品详情 API 接口 JSON 格式返回数据解析的示例
本文介绍商品详情API接口返回的JSON数据解析。最外层为`product`对象,包含商品基本信息(如id、name、price)、分类信息(category)、图片(images)、属性(attributes)、用户评价(reviews)、库存(stock)和卖家信息(seller)。每个字段详细描述了商品的不同方面,帮助开发者准确提取和展示数据。具体结构和字段含义需结合实际业务需求和API文档理解。
1078 37
|
人工智能 搜索推荐 API
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
cobalt 是一款开源的流媒体下载工具,支持全平台视频、音频和图片下载,提供纯净、简洁无广告的体验
3132 9
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
1395 4
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
Kubernetes 容器 Perl
【赵渝强老师】K8s中Pod中的初始化容器
Kubernetes的Pod包含业务容器、基础容器、初始化容器和临时容器。初始化容器在业务容器前运行,用于执行必要的初始化任务。本文介绍了初始化容器的作用、配置方法及优势,并提供了一个示例。
455 1
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
288 3
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
498 3
|
Python
深入解析 Python 中的对象创建与初始化:__new__ 与 __init__ 方法
深入解析 Python 中的对象创建与初始化:__new__ 与 __init__ 方法
457 1
|
SQL Shell 数据库
在TDengine容器中创建初始化数据库的Shell命令实例
以上就是在Docker容器环境中部署并初始化TDengine数据库的全过程,希望对你有所帮助。
755 0
|
消息中间件 Kubernetes 数据库
在k8S中,初始化容器(init container)概念原理是什么?
在k8S中,初始化容器(init container)概念原理是什么?

推荐镜像

更多
  • DNS