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 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3267 1
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1522 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
609 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
510 18
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
4175 1
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
848 1
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

推荐镜像

更多
  • DNS