文档章节

Spring源码阅读-BeanFactory初始化-配置加载

Small-Liu
 Small-Liu
发布于 2016/12/09 17:01
字数 1336
阅读 217
收藏 0

一、配置加载

每个程序启动都要加载配置,只是不同程序读取配置方式不同,spring也有一套自己规则的配置方式,spring通过beanFactory来加载配置、管理对象,BeanFactory子类树是非常复杂的,如果每一个都看非常耗时间,可以找一个典型的子类看一下它的初始化过程,以XmlBeanFactory为例看spring加载配置。
看XmlBeanFactory的入口:

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
   super(parentBeanFactory);
   this.reader.loadBeanDefinitions(resource);
}

通过XmlBeanDefinitionReader 加载配置资源, 开始跟踪loadBeanDefinitions的代码,发现XmlBeanDefinitionReader 通过DefaultDocumentLoader对配置文件进行校验并转换成document,到这里配置就算是验证并加载到内存中了,下面就是通过BeanDefinitionDocumentReader解析doc标签,看下面的代码:

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		//获取根节点
		Element root = doc.getDocumentElement();
		//从根节点开始解析标签
		doRegisterBeanDefinitions(root);
	}
	
	protected void doRegisterBeanDefinitions(Element root) {
		//获取beans标签的profile属性
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec,
					BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			//看spring.profiles.active环境变量中是否有该属性,如果没有则不加载下面的标签
			if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
				return;
			}
		}

		//标签beans可能会存在递归的情况, 每次都创建自己的解析器
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(this.readerContext, root, parent);

		//解析前处理、留给子类实现
		preProcessXml(root);
		//解析bean definition
		parseBeanDefinitions(root, this.delegate);
		//解析后处理、留给子类实现
		postProcessXml(root);

		this.delegate = parent;
	}
}

上面就是从根节点开始解析doc,这里支持标签beans嵌套的情况,这样方便我们在发布的时候改一个环境变量就可以切换多套系统配置,比如如下配置:

<beans>
    <beans profile="dev">
    ...
    </beans>
    <beans profile="test">
    ...
    </beans>
</beans>
在web中使用,在web.xml配置环境变量
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>

加载配置的时序图如下:

下面就是解析beans下面的标签了,通过命名空间判断是默认标签还是自定义标签来走不同的流程。

二、默认标签解析

分四种情况import alais bean beans:

  1. import: 解析出resource属性获取依赖配置资源用XmlBeanDefinitionReader递归加载配置
  2. alais:  把别名注册到beanFactory中的aliasMap,需要判断不能循环起别名,如:a->b,b->a
  3. beans:递归调用解析根节点的代码doRegisterBeanDefinitions
  4. bean:用BeanDefinitionParserDelegate解析封装成成BeanDefinitionHolder,把解析后的BeanDefinitionHolder注册到beanFactory。到BeanDefinitionParserDelegate看BeanDefinitionHolder是怎么创建的,先创建BeanDefinition,然后把该标签下面的属性、子标签都set进BeanDefinition,最后把BeanDefinition、beanName和alias数组一起封装成BeanDefinitionHolder,看BeanDefinition的属性基本可以看出bean标签下面可以配置哪些属性。

重点看一下解析bean标签的时序图:

三、自定义标签解析

自定义标签在META-INF/spring.handlers定义的,通过标签的命名空间获取到对应的处理类handler,然后调用handler.parse方法,这里主要看命名空间跟handler的对应关系是怎么加载的。

看BeanDefinitionParserDelegate的parseCustomElement方法:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	//这个resolve方法里面很重要, namespace的Handler是在这里加载的
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

到resolve中看是怎么获取的NamespaceHandler:

public NamespaceHandler resolve(String namespaceUri) {
	//加载所有handlers配置
	Map<String, Object> handlerMappings = getHandlerMappings();
	//获取到对应handler的handler对象或者字符串
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	if (handlerOrClassName == null) {
		return null;
	} else if (handlerOrClassName instanceof NamespaceHandler) {
		//如果已经初始化过直接返回
		return (NamespaceHandler) handlerOrClassName;
	} else {
		//未初始化的通过反射实例化对象,并且调用init方法,init方法是把标签跟对应的parser做映射
		String className = (String) handlerOrClassName;
		try {
			Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
			if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
				throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri
						+ "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
			}
			NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
			namespaceHandler.init();
			handlerMappings.put(namespaceUri, namespaceHandler);
			return namespaceHandler;
		} catch (ClassNotFoundException ex) {
			throw new FatalBeanException(
					"NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found",
					ex);
		} catch (LinkageError err) {
			throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace ["
					+ namespaceUri + "]: problem with handler class file or dependent class", err);
		}
	}
}
private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
					//把properties文件加载到map中
					Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,
							this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded NamespaceHandler mappings: " + mappings);
					}
					Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
					CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
					this.handlerMappings = handlerMappings;
				} catch (IOException ex) {
					throw new IllegalStateException("Unable to load NamespaceHandler mappings from location ["
							+ this.handlerMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.handlerMappings;
}





选一个NamespaceHandler看看它的init方法是做什么的:

public class AopNamespaceHandler extends NamespaceHandlerSupport {

	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the '
	 * {@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}
}

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		//元素解析器放入parsers 集合中:
		this.parsers.put(elementName, parser);
	}
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//这里调用BeanDefinitionParser的parse方法
		return findParserForElement(element, parserContext).parse(element, parserContext);
	}
	//获取BeanDefinitionParser
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		String localName = parserContext.getDelegate().getLocalName(element);
		//从前面加载的集合中根据标签名称获取Parser
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]",
					element);
		}
		return parser;
	}
}

到这里NamespaceHandler就获取到了,回归到上面parseCustomElement方法,handler调用parse方法,parse方法里面首先根据标签到parsers集合里面找到对应的解析器并调用对应解析器的parse方法,时序图:

© 著作权归作者所有

共有 人打赏支持
Small-Liu
粉丝 16
博文 56
码字总数 49976
作品 0
南京
程序员
向Spring大佬低头——大量源码流出解析

用Spring框架做了几年的开发,只停留在会用的阶段上,然而Spring的设计思想和原理确实一个巨大的宝库。大部分人仅仅知道怎么去配,或着加上什么属性就能达到什么效果,这些东西都可以通过查文...

Java团长17
07/11
0
0
Spring核心源码:ApplicationContext

废话 spring版本:4.0.6 随便做了这么多年的spring,但是源码就只阅读过 shiro的。一直想去读一下spring,mybatis,netty,这些结构优美的开源框架的源码。 核心包: spring-context:spring的上...

GITTODO
04/25
0
0
Spring源码解析系列之IOC容器(一)

前言 实际上我所有的博客都是原来对原来印象笔记里笔记内容的加工,关于Spring源码自己已经解析了很多遍,但是时间长总是忘记,写一篇博客权当加强记忆,也算再次学习下大师们的设计思想,思...

后厂村老司机
06/02
0
0
spring源码解析上下文初始化ContextLoaderListener

前言 本文转自“天河聊技术”微信公众号 从本篇文章开始主要介绍spring源码解析相关的spring上下文初始化、bean定义解析、beanFactory创建、初始化、bean定义注册到beanFactory、bean实例化、...

天河2018
05/10
0
0
Spring IOC 实现原理

Spring IOC 实现原理 IOC: Inversion of Control ,即 "控制反转" , 不是什么技术,而是一种思想。原先需要自行实例化的对象, 交给IOC容器去实现。那么控制反转,谁被控制? 谁被反转 ? 在...

起个名忒难
05/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

SpringCloud SpringBoot mybatis分布式Web应用的统一异常处理

我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用...

itcloud
5分钟前
0
0
c++ std::bind和std::function

定义于头文件 <functional> std::bind 函数绑定,https://zh.cppreference.com/w/cpp/utility/functional/bind // bind 用例#include <iostream>#include <functional> // 自定义的一......

SibylY
7分钟前
0
0
SecureCRT的安装与破解(过程很详细!!!)

SecureCRT的安装与破解(过程很详细!!!) SecureCRT的安装与破解(过程很详细!!!) 使用SecureCRT可以方便用户在windows环境下对linux主机进行管理,这里为大家讲一下SecureCRT的破解方...

DemonsI
12分钟前
0
0
介绍几款可用的web应用防火墙

目前有两款,基于软件和基于应用程序的web应用防火墙。基于软件的产品布置在Web服务器上,而基于应用程序的产品放置在Web服务器和互联网接口之间。两种类型的防火墙都会在数据传入和传出web...

上树的熊
18分钟前
0
0
用Visual Studio开发以太坊智能合约

区块链和以太坊 自从我熟悉区块链、以太坊和智能合约以来,一直失眠。 我一直在阅读,阅读和阅读,最后我能够使用一些工具,他们建议使用以太坊网站官方客户端应用程序(Ethereum Wallet)也...

geek12345
20分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部