spring源码--IOC容器的实现:资源文件的加载过程

引言

上一篇从总体了解的spring IOC容器的过程–大致可以分为资源定位,解析bean,初始化bean三个过程,那我们就分析一下spring如何对资源定位以及加载的。

加载过程

我们还是以ClassPathXmlApplicationContext为例,上篇说到,资源的加载是由AbstractApplicationContext的obtainFreshBeanFactory()方法开始的,这个方法最终返回了一个包含bean定义信息的BeanFactory。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	refreshBeanFactory();
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	return beanFactory;
}

而具体又是在refreshBeanFactory()中载入和解析的,refreshBeanFactory()是一个抽象方法,交由子类AbstractRefreshableApplicationContext来实现,如下:

@Override
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		//创建一个空的BeanFactory
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		//对BeanFactory做一些设置
		customizeBeanFactory(beanFactory);
		//载入资源并解析bean定义
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

我们接着进入loadBeanDefinitions()方法来看具体的过程,同样loadBeanDefinitions()也是抽象方法,具体交由AbstractXmlApplicationContext来实现,如下:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 创建一个读取器
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	beanDefinitionReader.setEnvironment(this.getEnvironment());
	//因为AbstractXmlApplicationContext继承了DefaultResourceLoader,所以资源加载器设置为了自身
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	//允许子类对beanDefinitionReader做一些定制
	initBeanDefinitionReader(beanDefinitionReader);
	//具体载入过程
	loadBeanDefinitions(beanDefinitionReader);
}

进入loadBeanDefinitions()

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);
		}
	}

这里讲具体的载入过程叫给XmlBeanDefinitionReader来实现。进入reader.loadBeanDefinitions()

@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
	Assert.notNull(locations, "Location array must not be null");
	int counter = 0;
	for (String location : locations) {
		counter += loadBeanDefinitions(location);
	}
	return counter;
}

进入loadBeanDefinitions(location)

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(location, null);
}

进入loadBeanDefinitions(location, null);

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//获取资源加载器
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			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);
			}
		}
		else {
			// 通过绝对路径定位资源
			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;
		}
	}

我们回顾一下整体的流程
这里写图片描述
##资源定位
这里我们关注一下具体是怎么定位资源的
通过((ResourcePatternResolver) resourceLoader).getResources(location)来获取到对应的资源,同样org.springframework.core.io.support.ResourcePatternResolver#getResources是个抽象方法,这里由于我们使用的是ClassPathXmlApplicationContext,所以由PathMatchingResourcePatternResolver来具体实现:

@Override
public Resource[] getResources(String locationPattern) throws IOException {
	Assert.notNull(locationPattern, "Location pattern must not be null");
	if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
		//如果路径以classpath*: 开头并且路径中含有通配符,将会递归获取资源
		if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
		
			return findPathMatchingResources(locationPattern);
		}
		//以classpath*开头,将会从本地和所有的jar包中获取
		else {
			return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
		}
	}
	else {
		//去除前缀
		int prefixEnd = locationPattern.indexOf(":") + 1;
		if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
			// a file pattern
			return findPathMatchingResources(locationPattern);
		}
		else {
			//直接获取
			return new Resource[] {getResourceLoader().getResource(locationPattern)};
		}
	}
}

通过这个方法,将路径转换为了spring可以使用的Resource对象。具体实现可以参照源码,因为代码比较细碎,这里不再多讲。

资源文件的解析

在org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource…)方法中,定义了解析的逻辑。

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
	Assert.notNull(resources, "Resource array must not be null");
	int counter = 0;
	for (Resource resource : resources) {
		counter += loadBeanDefinitions(resource);
	}
	return counter;
}

对于资源文件,循环解析。而loadBeanDefinitions(resource)交由子类实现。
以XmlBeanDefinitionReader为例:

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);
		}
		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();
			}
		}
	}

接着进入doLoadBeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			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);
		}
	}

可以看出这个方法中最终将输入流解析成了Document,至此,spring如何将path解析为spirng可以使用的Document已经完成了。由于篇幅有限,可能有许多细节的方法没有讲到。但是掌握了成体脉络,接下来对于看细节相信也不是什么难事。
下一篇我们将分析spring是如何将Document解析为BeanDefinition并注册进BeanFactory中的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章