Spring IOC - 资源装载

很多时候,真心欣赏 Spring 的封装、分层的方案实现。Spring 对资源文件的统一定义以及获取都是很好封装体现。

 

1. 资源装载的概述

 

在前面的分享中,使用 FileSystemXmlApplicationContext 举例。同时提到了在构建 FileSystemXmlApplicationContext 的时候会调用org.springframework.context.support.AbstractApplicationContext#refresh方法。在这个 refresh 方法中会调用AbstractApplicationContext中的refreshBeanFactory方法,这个方法就是加载资源去触发点。下图是是具体调用过程一览:

 

具体实现参考AbstractRefreshableApplicationContext#refreshBeanFactory实现。在这个方法中有 Bean 定义文件加载的入口:AbstractRefreshableApplicationContext#loadBeanDefinitions

 

同时有必要把资源加载接口在 Spring 体系中整理一下,有个统一的感性认识:

上面类图有两个地方需要说明一下

▪ PathMatchingResourcePatternResolver的实现

PathMatchingResourcePatternResolver 自己并不真实的执行加载资源操作,他依靠了外部传入的 ResourceLoader。如果外部没有传入,他就默认了 DefaultResourceLoader。有一点适配器模式的意思,他把 ResourceLoader 当成自己的一部分,否则他无法进行工作。

同时 PathMatchingResourcePatternResolver 中依赖了 AntPathMatcher,通过 AntPathMatcher 来处理匹配定义在资源文件名称,同时也支持了在Jar中获取资源的方式方法,如果读者感兴趣,可以查找对应的源码。

 

▪ AbstractApplicationContext的实现

AbstractApplicationContext 继承了 DefaultResourceLoader,同时实现了 ResourcePatternResolver。继承 DefaultResourceLoader,使其具有了 ResourceLoader 的服务能力,实现了 ResourcePatternResolver,使其具有了使用资源名称表达式的方式查找资源的能力。值得一提的是,他实现了 ResourcePatternResolver,并没有自己重新实现,而是让 PathMatchingResourcePatternResolver 作为真正服务的提供者。

 

2. 分析重要源码

 

2.1 AbstractXmlApplicationContext 中加载资源入口

 

还是以 FileSystemXmlApplicationContext 会使用到的实现为例来说明。参考实现代码:org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)

 

// 加载Bean定义资源
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// Create a new XmlBeanDefinitionReader for the given BeanFactory.
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// Configure the bean definition reader with this context's
	// 资源装载的环境
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	// Application 本身就是一个资源装载器,因为他继承了DefaultResourceLoader
	beanDefinitionReader.setResourceLoader(this);
	// 定义 xml 的实体,在解析 xml 时使用
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.
	initBeanDefinitionReader(beanDefinitionReader);
	// 真正的装载资源入口
	loadBeanDefinitions(beanDefinitionReader);
}
// 真正的装载资源入口
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
	Resource[] configResources = getConfigResources();
	if (configResources != null) {
		reader.loadBeanDefinitions(configResources);
	}
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		reader.loadBeanDefinitions(configLocations);
	}
}
通过上面代码,我们了解到 AbstractXmlApplicationContext 并没有真正的加载 Bean 定义文件,他把这项艰巨的工作委托给了XmlBeanDefinitionReader。

 

2.2 XmlBeanDefinitionReader

首先看看上面构建 XmlBeanDefinitionReader 代码,他最终会使用下面代码进行实例的创建。

 

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 {
		this.environment = new StandardEnvironment();
	}
}
我们知道 AbstractXmlApplication 他本身就是实现了 BeanDefinitionRegistry 和 ResourceLoader 的,所以构建的 XmlBeanDefinitionReader 中的 resourceLoader registry 都是 Application 自己。

 

经过一层一层的转化和转发,最终完成使命,成功把自己的加载的任务成功转交给BeanDefinitionParserDelegate。

 

下面是重要节点源码分析:

 

▪ AbstractBeanDefinitionReader#loadBeanDefinitions(String, Set<Resource>)

 

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// AbstractXmlApplicationContext 实现了 ResourceLoader 接口,所以到运行时就是其子类的真实实例。
	ResourceLoader resourceLoader = getResourceLoader();
	if (resourceLoader == null) {
		throw new BeanDefinitionStoreException(
				"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
	}
// 所有的 Application 都是实现了 ResourcePatternResolver 接口。在前面提到过 ApplicationContext 的实现
// ResourcePatternResolver 的方式。通过这种匹配算法,可以加载多个资源文件。
	if (resourceLoader instanceof ResourcePatternResolver) {
		// Resource pattern matching available.
		try {
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
			// 后面说明这个方法
			int loadCount = loadBeanDefinitions(resources);
			if (actualResources != null) {
				for (Resource resource : resources) {
					actualResources.add(resource);
				}
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
			}
			return loadCount;
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Could not resolve bean definition resource pattern [" + location + "]", ex);
		}
	}
	// 如果不是 ResourcePatternResolver 只能加载一个资源文件
	else {
		// Can only load single resources by absolute URL.
		Resource resource = resourceLoader.getResource(location);
		// 后面说明这个方法
		int loadCount = loadBeanDefinitions(resource);
		if (actualResources != null) {
			actualResources.add(resource);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
		}
		return loadCount;
	}
}
 
▪ XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource)

在AbstractBeanDefinitionReader#loadBeanDefinitions(String, Set<Resource>)中调用了loadBeanDefinitions方法,会转发到这个方法。

 

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isInfoEnabled()) {
		logger.info("Loading XML bean definitions from " + encodedResource.getResource());
	}

	// 记录当前线程解析的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<EncodedResource>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
// 把当前解析的资源加到当前线程中,如果加入失败,说明出现了循环依赖。
// 例如有资源文件bean-a.xml 引用了 bean-b.xml ,bean-b.xml 引用 bean-c.xml ,bean-c.xml 引用了 bean-a.xml
	// 他们就出现了循环依赖
	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();
		}
	}
}
▪ XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource, Resource)

这个方法是真正加载资源的地方,获取对xml进行验证的模式,以及读取xml到 Document 中。其中关于 EntityResolver 的内容会在后续的分享中详细讲解。

 

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		int validationMode = getValidationModeForResource(resource);
// 加载资源,解析XML文件变成可用的 document 元素,方便后面使用。具体的内容请查看 Spring 源码。
		Document doc = this.documentLoader.loadDocument(
				inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
// 十分重要的方法,注册Bean定义,后面会详细讲解。
		return registerBeanDefinitions(doc, resource);
	}
	catch (BeanDefinitionStoreException ex) {
		throw ex;
	}
	catch (SAXParseException ex) {
		throw new XmlBeanDefinitionStoreException(resource.getDescription(),
				"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
	}
	catch (SAXException ex) {
		throw new XmlBeanDefinitionStoreException(resource.getDescription(),
				"XML document from " + resource + " is invalid", ex);
	}
	catch (ParserConfigurationException ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"Parser configuration exception parsing XML from " + resource, ex);
	}
	catch (IOException ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"IOException parsing XML document from " + resource, ex);
	}
	catch (Throwable ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"Unexpected exception parsing XML document from " + resource, ex);
	}
}
 
▪ XmlBeanDefinitionReader#registerBeanDefinitions(Document, Resource)

下面的代码在下次分享中着重来讲

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	documentReader.setEnvironment(this.getEnvironment());
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值