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,如需转载请自行联系原作者



相关文章
|
4月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
11月前
|
监控 Java 应用服务中间件
微服务——SpringBoot使用归纳——为什么学习Spring Boot
本文主要探讨为什么学习Spring Boot。从Spring官方定位来看,Spring Boot旨在快速启动和运行项目,简化配置与编码。其优点包括:1) 良好的基因,继承了Spring框架的优点;2) 简化编码,通过starter依赖减少手动配置;3) 简化配置,采用Java Config方式替代繁琐的XML配置;4) 简化部署,内嵌Tomcat支持一键式启动;5) 简化监控,提供运行期性能参数获取功能。此外,从未来发展趋势看,微服务架构逐渐成为主流,而Spring Boot作为官方推荐技术,与Spring Cloud配合使用,将成为未来发展的重要方向。
430 0
微服务——SpringBoot使用归纳——为什么学习Spring Boot
|
6月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
10月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
601 70
|
8月前
|
安全 Java 数据库
Spring Boot 框架深入学习示例教程详解
本教程深入讲解Spring Boot框架,先介绍其基础概念与优势,如自动配置、独立运行等。通过搭建项目、配置数据库等步骤展示技术方案,并结合RESTful API开发实例帮助学习。内容涵盖环境搭建、核心组件应用(Spring MVC、Spring Data JPA、Spring Security)及示例项目——在线书店系统,助你掌握Spring Boot开发全流程。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
1523 3
|
10月前
|
Java Spring
Spring框架的学习与应用
总的来说,Spring框架是Java开发中的一把强大的工具。通过理解其核心概念,通过实践来学习和掌握,你可以充分利用Spring框架的强大功能,提高你的开发效率和代码质量。
243 20
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
3404 11
|
11月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
297 0

推荐镜像

更多
  • DNS