Spring源码剖析-IOC启动流程

Posted 墨家巨子@俏如来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码剖析-IOC启动流程相关的知识,希望对你有一定的参考价值。

前言

这篇文章是接上一篇文章《IOC启动流程(二)》,上一章节我们见到了Spring IOC容器的容器创建和配置加载两个大的流程,接来下分析Bean的解析以及Bean的注册流程。这里我终于可以把IOC启动流程的大图放上来了,你可以根据该图来看我接下来的流程分析
在这里插入图片描述

Bean的解析:XmlBeanDefinitionReader

Spring IOC启动创建完容器之后,最终委托XmlBeanDefinitionReader#loadBeanDefinitions加载Bean。

XmlBeanDefinitionReader通过 ResourcePatternResolver 加载配置XML转换为 Resource对象并封装成InputSource 文档解析源,然后XmlBeanDefinitionReader通过DocumentLoader.loadDocument 把InputSource(XML配置文件)解析成Document。最后调用registerBeanDefinitions方法解析和注册Bean :见XmlBeanDefinitionReader#loadBeanDefinitions源码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			//将 XML 文件转换为 Document 对象,通过 documentLoader来解析
			Document doc = doLoadDocument(inputSource, resource);
			//【重要】解析和注册Bean的消息流程
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		...省略...

下面是:DefaultDocumentLoader#loadDocument 加载文档源码:

//XML解析成Document,调用documentLoader.loadDocument完成
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
}
	
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
		//创建文档解析工厂
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		//创建文档解析器
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		//执行解析了
		return builder.parse(inputSource);
	}

下面是registerBeanDefinitions方法,即:Bean的解析和注册流程:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//Bean的文档解析器,由 DefaultBeanDefinitionDocumentReader 实现
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//容器中Bean的数量
		int countBefore = getRegistry().getBeanDefinitionCount();
		//通过 DefaultBeanDefinitionDocumentReader .registerBeanDefinitions 注册Bean
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//统计解析的Bean的个数
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

这里创建了DefaultBeanDefinitionDocumentReaderBean的文档解析器,调用其registerBeanDefinitions方法来注册Bean,见:DefaultBeanDefinitionDocumentReader#registerBeanDefinitions

DefaultBeanDefinitionDocumentReader#registerBeanDefinitions

DefaultBeanDefinitionDocumentReader委派BeanDefinitionParserDelegate进行Document的解析工作

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	//获取根元素,调用doRegisterBeanDefinitions方法
	doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {
		//创建BeanDefinitionParserDelegate,它是Bean解析委派器,定义了XML的各种元素的解析
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		//解析之前的动作,空方法,可以通过复写该方法做扩展
		preProcessXml(root);
		//解析Document,从root开始,委派给BeanDefinitionParserDelegate来解析
		parseBeanDefinitions(root, this.delegate);
		//解析之后的动作,空方法,可以通过复写该方法做扩展
		postProcessXml(root);

		this.delegate = parent;
	}

该方法中创建了BeanDefinitionParserDelegate,它定义了Spirng的xml中的所有的元素,然后委派它来对Document进行解析,继续跟 BeanDefinitionParserDelegate.parseBeanDefinitions 方法

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//判断是否使用默认的Spring的xml默认的命名空间
		if (delegate.isDefaultNamespace(root)) {
			//获取根元素下的所有元素
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				//判断节点是XML的节点
				if (node instanceof Element) {

					Element ele = (Element) node;
					//判断命名空间
					if (delegate.isDefaultNamespace(ele)) {
						//按照Spring默认的命名空间解析元素
						parseDefaultElement(ele, delegate);
					}
					else {
						//使用自定义的规则解析
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//使用自定义的规则解析
			delegate.parseCustomElement(root);
		}
	}

这里判断了命名空间之后,调用 parseDefaultElement方法进行解析文档

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//如果Element是 <import> 元素,就执行 importBeanDefinitionResource 导入其他配置文件
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//如果是 <alias> 就调用processAliasRegistration 注册 alias别名
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//如果是 <bean> 元素就走 processBeanDefinition,解析Bean
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		//如果是 <beans> 就走 doRegisterBeanDefinitions 注册多个Bean
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

这里判断了 import ,alias ,bean ,beans几种元素,分别走不同的方法进行解析。

Import 解析

下面是对 importBeanDefinitionResource 方法的分析 ,该方法的主要作用是解析<import 导入的配置文件,然后把其中的Bean的解析出来注册到Spring容器中,还是调用的是 XmlBeanDefinitionReader#loadBeanDefinitions方法来完成Bean的加载的。

/**
	 * Parse an "import" element and load the bean definitions
	 * from the given resource into the bean factory.
	 */
	protected void importBeanDefinitionResource(Element ele) {
		//得到<import resource="配置路径"  resouce即:导入的配置路径
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		if (!StringUtils.hasText(location)) {
			//没导入任何文件,直接返回
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}
		//解析系统属性,解析占位符
		// Resolve system properties: e.g. "${user.dir}"
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

		Set<Resource> actualResources = new LinkedHashSet<>(4);
		//给定导入的文件的位置是绝对URI还是相对URI
		// Discover whether the location is an absolute or relative URI
		boolean absoluteLocation = false;
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {
			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
		}

		//绝对路径
		// Absolute or relative?
		if (absoluteLocation) {
			try {
				//使用XmlBeanDefinitionReader加载导入的配置文件
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
				if (logger.isTraceEnabled()) {
					logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
				}
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
			}
		}
		else {
			//相对路径
			// No URL -> considering resource location as relative to the current file.
			try {
				int importCount;
				//把配置文件加载成相对路径的Resource对象 ,就是要找到导入读文件,然后返回它的Resource对象
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				if (relativeResource.exists()) {
					//如果文件存在,使用XmlBeanDefinitionReader加载导入的配置文件
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
				else {
					//如果不存在,使用资源加载器的相对路径来拼接文件地址,然后使用XmlBeanDefinitionReader加载导入的配置文件
					String baseLocation = getReaderContext().getResource().getURL().toString();
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
				}
			}
			catch (IOException ex) {
				getReaderContext().error("Failed to resolve current resource location", ele, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from relative location [" + location + "]", ele, ex);
			}
		}
		Resource[] actResArray = actualResources.toArray(new Resource[0]);
		//发布一个import成功的事件
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

alias 别名注册

下面是对 processAliasRegistration 方法的解析,方法的作用是 解析<alias 给定的别名元素,向注册表注册别名。

/**
	 * Process the given alias element, registering the alias with the registry.
	 */
	protected void processAliasRegistration(Element ele) {
		//获取 元素的name属性
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		//获取元素的alias属性
		String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
		//做了一下判断,name和alias是否为空
		boolean valid = true;
		if (!StringUtils.hasText(name)) {
			getReaderContext().error("Name must not be empty", ele);
			valid = false;
		}
		if (!StringUtils.hasText(alias)) {
			getReaderContext().error("Alias must not be empty", ele);
			valid = false;
		}
		if (valid) {
			//如果定义了name或者alis
			try {
				//注册alias ,调用 AliasRegistry 的 registerAlias 来注册
				//默认实现 .SimpleAliasRegistry#registerAlias
 				getReaderContext().getRegistry().registerAlias(name, alias);
			}
			catch (Exception ex) {
				getReaderContext().error("Failed to register alias '" + alias +
						"' for bean with name '" + name + "'", ele, ex);
			}
			//alias注册完之后,发布一个事件
			getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
		}
	}

上面方法 获取到元素的name和alias属性后,调用 SimpleAliasRegistry#registerAlias 来注册Bean的名字,其实就是使用一个ConcurrentHashMap来存储。

public class SimpleAliasRegistry implements AliasRegistry {

	/** Logger available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());

	/** Map from alias to canonical name. */
	private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
	...省略...
	
	@Override
	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		//加锁
		synchronized (this.aliasMap) {
			if (alias.equals(name)) {
				//如果name和alias相同,从map中移除
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}
			else {
				//通过alias获取map中的值
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
					if (registeredName.equals(name)) {
						// An existing alias - no need to re-register
						return;
					}
					if (!allowAliasOverriding()) {
						throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'");
					}
				}
				//判断名字循环引用问题
				checkForAliasCircle(name, alias);
				//以alias为key name为值保持到ma
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}

bean 的解析

下面是对 processBeanDefinition方法的解析,将解析的工作委托给BeanDefinitionParserDelegate,然后通过BeanDefinitionReaderUtils调用BeanDefinitionRegistry进行Bean的注册

	/**
	 * Process the given bean element, parsing the bean definition
	 * and registering i

以上是关于Spring源码剖析-IOC启动流程的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码剖析-基于注解的IOC启动流程

Spring源码剖析-基于注解的IOC启动流程

Spring源码剖析1:初探Spring IOC核心流程

Spring IOC源码剖析:Spring IOC容器初始化主体流程

Spring IOC源码剖析:BeanFactory创建流程及Bean创建流程

深入浅出Spring原理及实战「原理分析专题」不看源码就带你剖析IOC容器核心流程以及运作原理