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



相关文章
|
11月前
|
Kubernetes Cloud Native 持续交付
Docker:轻量级容器化技术解析
Docker:轻量级容器化技术解析
|
存储 缓存 安全
Java 集合容器常见面试题及详细解析
本文全面解析Java集合框架,涵盖基础概念、常见接口与类的特点及区别、底层数据结构、线程安全等内容。通过实例讲解List(如ArrayList、LinkedList)、Set(如HashSet、TreeSet)、Map(如HashMap、TreeMap)等核心组件,帮助读者深入理解集合容器的使用场景与性能优化。适合准备面试或提升开发技能的开发者阅读。
247 0
|
弹性计算 Java Maven
从代码到容器:Cloud Native Buildpacks技术解析
Cloud Native Buildpacks(CNB)是一种标准化、云原生的容器镜像构建系统,旨在消除手动编写Dockerfile,提供可重复、安全且高效的构建流程。它通过分层策略生成符合OCI标准的镜像,实现应用与基础镜像解耦,并自动化依赖管理和更新。阿里云应用管理支持通过CNB技术一键部署应用至ECS,简化构建和运行流程。
|
JSON 前端开发 搜索推荐
关于商品详情 API 接口 JSON 格式返回数据解析的示例
本文介绍商品详情API接口返回的JSON数据解析。最外层为`product`对象,包含商品基本信息(如id、name、price)、分类信息(category)、图片(images)、属性(attributes)、用户评价(reviews)、库存(stock)和卖家信息(seller)。每个字段详细描述了商品的不同方面,帮助开发者准确提取和展示数据。具体结构和字段含义需结合实际业务需求和API文档理解。
1018 37
|
人工智能 搜索推荐 API
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
cobalt 是一款开源的流媒体下载工具,支持全平台视频、音频和图片下载,提供纯净、简洁无广告的体验
3039 9
Cobalt:开源的流媒体下载工具,支持解析和下载全平台的视频、音频和图片,支持多种视频质量和格式,自动提取视频字幕
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
1195 1
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
4540 11
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
603 4
|
安全 持续交付 Docker
深入理解并实践容器化技术——Docker 深度解析
深入理解并实践容器化技术——Docker 深度解析
700 2

推荐镜像

更多
  • DNS