spring源码03: 资源文件读取

接下去将顺着流程图中6大转化过程,跟踪代码,逐一讲解
spring解析阶段.jpg


Xml文件 -> Resource

xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
  1. 创建ClassPathResource对象
// ClassPathResource.java
	public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
		// 路径不允许为空
		Assert.notNull(path, "Path must not be null");
		// 规范路径
		String pathToUse = StringUtils.cleanPath(path);
		// 如果路径以/开头,去掉/
		if (pathToUse.startsWith("/")) {
			pathToUse = pathToUse.substring(1);
		}
		this.path = pathToUse;
		// 设置classLoader
		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	}
  1. ClassPathResource获取InputStream
// ClassPathResource.java
	public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			// 使用类对象的getResourceAsStream
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			// 使用类加载器的getResourceAsStream
			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;
	}

类加载器涉及面太宽,不是本专题的重点,有兴趣的可以参考下:
老大难的 Java ClassLoader


Resource -> InputStram

获取到Resource以后,继续跟踪代码

  1. 实例化XmlBeanFactory
// XmlBeanFactory.java
public XmlBeanFactory(Resource resource) throws BeansException {
		this(resource, null);
	}
// XmlBeanFactory.java
	public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		super(parentBeanFactory);
		// 核心逻辑,也是我们关注的重点
		this.reader.loadBeanDefinitions(resource);
	}
  1. 忽略指定接口的自动装配功能
    在初始化·XmlBeanFactory·中,需要先初始化父类,而父类中有这么一段代码需要注意。spring各类感知器也就是Aware的使用可以参考:spring BeanPostProcessor 生命周期
// AbstractAutowireCapableBeanFactory
	public AbstractAutowireCapableBeanFactory() {
		super();
		/**
		 * 自动装配时忽略给定的依赖接口
		 * 正常情况下,如果A类中有自动装配的属性B,则B如果未被创建则会被自动创建并自动装配到A当中
		 * 但是如果A实现了BeanFactoryAware,B是BeanFactory,则B不会被自动装配,而是通过调用重写的setBeanFactory方法进行注入
		 */
		ignoreDependencyInterface(BeanNameAware.class);
		ignoreDependencyInterface(BeanFactoryAware.class);
		ignoreDependencyInterface(BeanClassLoaderAware.class);
	}

  1. 初始化XmlBeanDefinitionReader对象
    XmlBeanFactory读取Xml的工作并没有自己完成,而是委托给了自己的属性reader,因此,在初始化XmlBeanFactory时,我们需要初始化XmlBeanDefinitionReader
// XmlBeanFactory.java
// 委托读取xml对象
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

初始化XmlBeanDefinitionReader最主要的工作是设置当前的资源加载器以及当前的相关环境变量,不做深究,将重点放在核心逻辑上

// XmlBeanDefinitionReader.java
	protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		// Determine ResourceLoader to use.
		if (this.registry instanceof ResourceLoader) {
			this.resourceLoader = (ResourceLoader) this.registry;
		}
		else {
			this.resourceLoader = new PathMatchingResourcePatternResolver();
		}

		// Inherit Environment if possible
		if (this.registry instanceof EnvironmentCapable) {
			// 有可继承的环境,则直接使用
			this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
		}
		else {
			// 否则新建一个环境,包括系统环境属性,JVM系统环境属性等
			this.environment = new StandardEnvironment();
		}
	}
  1. 对资源文件进行编码
// XmlBeanDefinitionReader.java
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		// 对资源进行编码
		return loadBeanDefinitions(new EncodedResource(resource));
	}
  1. 获取InputStream
    获取InputStream无非就是调用resourcegetInputStream方法,再进行编码操作。
    值得注意的是currentResources这个对象,该对象用于处理资源相互循环引用的检测。在spring中大量使用了这种思想,包括最著名的循环依赖也是使用这种方法进行检测的,需要多加理解
// XmlBeanDefinitionReader.java
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		/**
		 * currentResources保存着当前正在加载的资源,用于处理资源循环引用的问题
		 * 例如:A资源引入B,B又引入A资源。则加载A时会将B资源引入进来,于是currentResources中包含AB,
		 * 		而B资源又要将A资源引入,此时currentResources已经包含A,顾添加失败,抛出异常
		 */
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		// 资源正在加载,相互循环依赖,抛出异常
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			// 获取输入流
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				// 尝试对输入流进行编码
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// 核心逻辑
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				// 资源加载完成,删除
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

至此,Xml文件已经转化为InputStream,虽然关键代码只有几行,但是spring做了大量的工作,下一节将继续跟踪核心逻辑doLoadBeanDefinitions。在spring中有一个有趣规则,就是任何以do开头的函数才是真正的核心逻辑,因此以后我们看到do开头的函数,基本上算是熬出头了

发布了30 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chaitoudaren/article/details/104833473
今日推荐