前面介绍了Spring的一些基础知识和简单的用法,本篇开始分析Spring的IoC容器。BeanFactory是IoC容器的基础,所以接下来的分析都是基于BeanFactory的。

IoC启动过程:

启动
加载资源文件
解析资源文件
注册BeanDefinition
结束
IoC容器的启动可分为三步,加载资源文件、解析资源文件、注册BeanDefinition。本篇分析资源文件加载过程。

Spring中资源文件加载主要有两个接口,Resource和ResourceLoader,前者提供了对资源文件的定位、是否存在、是否可读、是否打开、是否文件、获取URL,获取File、获取FileName等一系列功能。后者提供了Resource对象获取,自定义资源文件协议解析等功能。加载资源文件的过程会涉及到类加载机制,且不是我们分析IoC容器的重点,所以本篇不会做太多深入的分析。测试类

import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;import java.io.IOException;public class MyTest {@Testpublic void test1() {// 从资源文件夹下加载Resource resource = new ClassPathResource("v2/day01.xml");print(resource);}@Testpublic void test2() {// 使用类信息加载Resource resource = new ClassPathResource("day01.xml", MyTest.class);print(resource);}@Testpublic void test3() {// 使用类加载器从资源文件夹下加载Resource resource = new ClassPathResource("v2/day01.xml", MyTest.class.getClassLoader());print(resource);}@Testpublic void test4() {// 使用DefaultResourceLoader加载Resource resource = new DefaultResourceLoader().getResource("v2/day01.xml");print(resource);}// 打印资源文件内容public void print(Resource resource) {byte[] read = new byte[10000];try {resource.getInputStream().read(read, 0, read.length);System.out.println(new String(read));} catch (IOException e) {e.printStackTrace();}}}

运行测试类,将会打印day01.xml的内容。注意资源文件的路径

先分析ClassPathResource的加载过程,再DefaultResourceLoader的加载过程

1. ClassPathResource对象创建过程
创建ClassPathResource对象

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {//规范路径String pathToUse = StringUtils.cleanPath(path);//如果路径以"/"开头,则截取开头"/"以后字符做为路径if (pathToUse.startsWith("/")) {pathToUse = pathToUse.substring(1);}//将处理后的路径赋给this.paththis.path = pathToUse;//获取classLoader并赋给this.classLoaderthis.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}

该构造函数比较简单,重点来看一下类加载器的获取过程。

获取类加载器

public static ClassLoader getDefaultClassLoader() {ClassLoader cl = null;try {//优先获取线程上下文类加载器cl = Thread.currentThread().getContextClassLoader();}catch (Throwable ex) {// Cannot access thread context ClassLoader - falling back...}if (cl == null) {// No thread context class loader -> use class loader of this class.// 获取当前类的类加载器cl = ClassUtils.class.getClassLoader();if (cl == null) {// getClassLoader() returning null indicates the bootstrap ClassLoadertry {//获取SystemClassLoadercl = ClassLoader.getSystemClassLoader();}catch (Throwable ex) {// Cannot access system ClassLoader - oh well, maybe the caller can live with null...}}}return cl;
}

类加载器简介:

bootstrap class loader:主要负责main方法启动的时候,加载JAVA_HOME/lib下的jar包
extension class loader:主要负责加载JAVA_HOME/ext/lib下的jar包
system class loader:主要负责加载classpath下的jar包或者类
2.使用ClassPathResource获取InputStream
创建了ClassPathResource对象实例之后,就可以使用该对象来获取InputStream。

public InputStream getInputStream() throws IOException {InputStream is;// ①如果类对象不为null,则使用类对象信息的getResourceAsStream获取输入流if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}// ②如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流else if (this.classLoader != null) {is = this.classLoader.getResourceAsStream(this.path);}else {// ③否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流is = ClassLoader.getSystemResourceAsStream(this.path);}if (is == null) {//以上三种方法都无法获取到输入流的话,那么说明文件不存在,抛出异常throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;
}

Spring对InputStream的获取过程进行了分情况处理

①如果类对象不为null,则使用类对象信息的getResourceAsStream获取输入流
②如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
③否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
使用类对象信息的getResourceAsStream获取输入流

public InputStream getResourceAsStream(String name) {// 解析资源文件名称// 如果名称不是绝对的,则添加包名称前缀。如果名称是绝对的,则删除前面的“/”// 例如:相对路径:解析前name=day01.xml;解析后name=com/lyc/cn/v2/day03/day01.xml//      绝对路径:解析前name=/day01.xml;解析后name=day01.xmlname = resolveName(name);// 获取类加载器并返回InputStreamClassLoader cl = getClassLoader0();if (cl==null) {// A system class.return ClassLoader.getSystemResourceAsStream(name);}return cl.getResourceAsStream(name);
}

使用类加载器的getResourceAsStream获取输入流

public InputStream getResourceAsStream(String name) {// 将资源文件路径转换为URL统一资源定位符URL url = getResource(name);try {if (url == null) {return null;}URLConnection urlc = url.openConnection();// 获取InputStreamInputStream is = urlc.getInputStream();// 判断URLConnection类型并根据其类型加入到closeables对象中// closeables对象通过WeakHashMap维护了Closeable对象信息if (urlc instanceof JarURLConnection) {JarURLConnection juc = (JarURLConnection)urlc;JarFile jar = juc.getJarFile();synchronized (closeables) {if (!closeables.containsKey(jar)) {closeables.put(jar, null);}}} else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {synchronized (closeables) {closeables.put(is, null);}}return is;} catch (IOException e) {return null;}
}

使用ClassLoader类的getSystemResourceAsStream方法获取输入流

public static InputStream getSystemResourceAsStream(String name) {// 将资源文件路径转换为URL统一资源定位符URL url = getSystemResource(name);try {// 判断URL对象是否为空,并返回InputStreamreturn url != null ? url.openStream() : null;} catch (IOException e) {return null;}
}

DefaultResourceLoader的加载过程

3.创建DefaultResourceLoader对象
该过程主要就是获取ClassLoader对象,与上面的分析是相同的。

public DefaultResourceLoader() {this.classLoader = ClassUtils.getDefaultClassLoader();
}public static ClassLoader getDefaultClassLoader() {ClassLoader cl = null;try {//优先获取线程上下文类加载器cl = Thread.currentThread().getContextClassLoader();}catch (Throwable ex) {// Cannot access thread context ClassLoader - falling back...}if (cl == null) {// No thread context class loader -> use class loader of this class.// 获取当前类的类加载器cl = ClassUtils.class.getClassLoader();if (cl == null) {// getClassLoader() returning null indicates the bootstrap ClassLoadertry {//获取SystemClassLoadercl = ClassLoader.getSystemClassLoader();}catch (Throwable ex) {// Cannot access system ClassLoader - oh well, maybe the caller can live with null...}}}return cl;
}

4.使用DefaultResourceLoader对象获取Resource

public Resource getResource(String location) {// ①优先遍历协议解决器集,如果可以解决,则返回位置相应的资源for (ProtocolResolver protocolResolver : this.protocolResolvers) {Resource resource = protocolResolver.resolve(location, this);if (resource != null) {return resource;}}// ②如果资源位置以"/"开头,则获取路径资源if (location.startsWith("/")) {return getResourceByPath(location);}// ③如果资源位置以"classpath:"开头,创建路径位置的的类路径资源ClassPathResourceelse 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...// ④尝试将路径转换为URL资源URL url = new URL(location);return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));}catch (MalformedURLException ex) {// No URL -> resolve as resource path.// ⑤没有成功转换为URL资源,则将location视为资源路径并返回对应解析资源return getResourceByPath(location);}}
}

该过程涉及到的步骤比较多,但是资源文件加载并不是我们分析IoC容器的重点,我们只分析自定义解析协议,其他的不再赘述,感兴趣的同学可以自己debug跟踪下代码。

5. 自定义协议解析器

import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class MyProtocolResolver implements ProtocolResolver {@Overridepublic Resource resolve(String location, ResourceLoader resourceLoader) {if (location.startsWith("my")) {return resourceLoader.getResource(location.replace("my", "classpath"));}return null;}
}

继承并实现ProtocolResolver接口的方法,将自定义的协议前缀转换为classpath即可

使用方法

@Test
public void test5() {// 使用自定义协议解析器加载DefaultResourceLoader resourceLoader = new DefaultResourceLoader();resourceLoader.addProtocolResolver(new MyProtocolResolver());Resource resource = resourceLoader.getResource("my:/v2/day01.xml");print(resource);
}

SpringMVC读取资源文件的几种方式相关推荐

  1. java读取XML文件的四种方式

    java读取XML文件的四种方式 Xml代码 <?xml version="1.0" encoding="GB2312"?> <RESULT& ...

  2. 用Python读取CSV文件的5种方式

    典型的数据集stocks.csv: 一个股票的数据集,其实就是常见的表格数据.有股票代码,价格,日期,时间,价格变动和成交量.这个数据集其实就是一个表格数据,有自己的头部和身体. 第一招:简单的读取 ...

  3. 第四章:数据存储-csv文件处理-读取csv文件的两种方式

    直接学习:https://edu.csdn.net/course/play/24756/280718 csv文件处理-读取csv文件的两种方式: # 这种方式读取到的每一条数据是个列表,所以需要通过下 ...

  4. java读取csv文件的两种方式

    java读取csv文件的两种方式 1.CsvReader读取 import com.csvreader.CsvReader; /*** CsvReader 读取* @param filePath* @ ...

  5. C# 读取XML文件的几种方式

    在开发过程中,我们有时会需要保存到本地一些结构化数据或者配置信息,这时就可以选择用xml文件.当然xml的用途也不仅仅是这些. 这一篇来谈一谈关于读取xml文件的几种方式: 我们有以下两个文件,一个是 ...

  6. C#读取资源文件的两种方法及保存资源文件到本地

    方法1 GetManifestResourceStream VB.NET中资源的名称为:项目默认命名空间.资源文件名 C#中则是:项目命名空间.资源文件所在文件夹名.资源文件名  例如: istr = ...

  7. C#读取资源文件的两种方法

    http://lwl0606.cmszs.com/?p=1021 方法1 GetManifestResourceStream VB.NET中资源的名称为:项目默认命名空间.资源文件名 C#中则是:项目 ...

  8. 使用Python读取本地文件的4种方式

    Python读取文件的4种方式,包括read().read(字节数).readlines().readline()方式. 1.read()方式,一次读取所有,返回str file0 = open(&q ...

  9. java中读取properties文件内容五种方式

    一.背景 最近,在项目开发的过程中,遇到需要在properties文件中定义一些自定义的变量,以供java程序动态的读取,修改变量,不再需要修改代码的问题.就借此机会把Spring+SpringMVC ...

  10. golang读取conf文件的两种方式(ini和Viper)

    文章目录 前言 一.ini包 1.下载 2.使用方法 法一:简单方法 法二:结构体反射 二.viper配置管理 1.下载 2.viper的特点 3.使用方法 读取 总结 前言 平时写项目都是习惯于将什 ...

最新文章

  1. idea中如何创建接口
  2. rails应用中各数据平台的对接
  3. openshift安装_云幸福–如何在几分钟内安装新的OpenShift Container Platform 3.7
  4. 【MyBatis笔记】使用注解开发
  5. MySQL中会用到age字段的索引_MySQL学习笔记(四):正确使用索引(二)
  6. 双标准线等角圆锥投影转换_青学堂--花花的地理课堂之地图投影知多少
  7. Security+ 学习笔记21 认证
  8. B和strong以及i和em的区别
  9. android修改便携式热点的默认SSID名称
  10. 【工具】一键制作纯净ROM去除推广APP,支持线刷包卡刷包
  11. kafka消息服务的producer、broker、consumer的配置
  12. 当display:flex弹性布局与position:absolute/fixed定位一起用,会出现的问题与解决方法
  13. 用最好的“积木”,在元宇宙中掀起一场头脑风暴吧!丨RTE 2022 编程挑战赛圆满收官...
  14. python爬虫--破解js加密:kankan登录破解
  15. 缓存和数据库如何保证一致性
  16. 微信分享接口内容限制 分享图片自己能看见别人看不见
  17. 电脑服务器显示一个f盘损坏,F盘异常如何修复呢?
  18. 戴尔计算机电源在哪里看,戴尔笔记本启动不来如何解决呢
  19. 董事长、总裁与CEO的区别与实质
  20. Mac笔记本上找不到my.cnf问题

热门文章

  1. 防止用户直接访问url
  2. jmeter元件的作用域与执行顺序
  3. 数据结构之线性表学习一
  4. Windows 8 Consumer Preview
  5. 【深度一键还原】我的台式机
  6. 解析WINDOWS中的DLL文件---经典DLL解读
  7. C# winform界面上传附件到服务器(springboot)
  8. 使用VSCode开发Electron的初步入门
  9. 43. 算法调用优先于手写的循环
  10. IDEA运行多个实例