文档章节

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

Small-Liu
 Small-Liu
发布于 2016/12/09 17:01
字数 1336
阅读 183
收藏 0
点赞 0
评论 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
粉丝 15
博文 56
码字总数 49976
作品 0
南京
程序员
Spring核心源码:ApplicationContext

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

GITTODO ⋅ 04/25 ⋅ 0

Spring源码解析系列之IOC容器(一)

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

后厂村老司机 ⋅ 06/02 ⋅ 0

spring源码解析上下文初始化ContextLoaderListener

前言 从本篇文章开始主要介绍spring源码解析相关的spring上下文初始化、bean定义解析、beanFactory创建、初始化、bean定义注册到beanFactory、bean实例化、依赖注入流程中相关的步骤,由于s...

天河2018 ⋅ 05/10 ⋅ 0

Spring IOC 实现原理

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

起个名忒难 ⋅ 05/17 ⋅ 0

深入理解Spring源码(一)-IOC容器的定位,载入,注册

前言:Spring源码继承,嵌套层次非常多,读起来非常容易晕,小伙伴们在看文章的时候一定要跟着文章的思路自己去源码里点一点,看一看,并且多看几次。就会越来越清晰。下面开始正题 1.Spring...

Meet相识_bfa5 ⋅ 05/01 ⋅ 0

spring源码解析bean定义加载一

前言 接着上次的spring上下文加载过程的源码解析 正文 上次主要介绍到刷新beanFactory,找到这个方法org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBe...

天河2018 ⋅ 05/10 ⋅ 0

Spring IOC 容器源码分析系列文章导读

1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了。Spring 包含了众多模块,包括但不限于...

coolblog ⋅ 05/30 ⋅ 0

Spring Bean注册解析(一)

Spring是通过IoC容器对Bean进行管理的,而Bean的初始化主要分为两个过程:Bean的注册和Bean实例化。Bean的注册主要是指Spring通过读取配置文件获取各个bean的声明信息,并且对这些信息进行注...

张旭峰 ⋅ 05/12 ⋅ 0

Spring IOC 容器源码分析 - 获取单例 bean

1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章。在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一些建议。在做完必要的准...

coolblog ⋅ 06/01 ⋅ 0

说说 Spring 容器的技术内幕

Spring 的 AbstractApplicationContext 是 ApplicationContext 的抽象实现类,这个类的 refresh() 方法定义了 Spring 容器在加载配置文件之后的处理过程: 下面列出重要的方法名与说明: 重点...

deniro ⋅ 05/17 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

VS中使用X64汇编

需要注意的是,在X86项目中,可以使用__asm{}来嵌入汇编代码,但是在X64项目中,再也不能使用__asm{}来编写嵌入式汇编程序了,必须使用专门的.asm汇编文件来编写相应的汇编代码,然后在其它地...

simpower ⋅ 今天 ⋅ 0

ThreadPoolExecutor

ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, ......

4rnold ⋅ 昨天 ⋅ 0

Java正无穷大、负无穷大以及NaN

问题来源:用Java代码写了一个计算公式,包含除法和对数和取反,在页面上出现了-infinity,不知道这是什么问题,网上找答案才明白意思是负的无穷大。 思考:为什么会出现这种情况呢?这是哪里...

young_chen ⋅ 昨天 ⋅ 0

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 昨天 ⋅ 0

实验楼—MySQL基础课程-挑战3实验报告

按照文档要求创建数据库 sudo sercice mysql startwget http://labfile.oss.aliyuncs.com/courses/9/createdb2.sqlvim /home/shiyanlou/createdb2.sql#查看下数据库代码 代码创建了grade......

zhangjin7 ⋅ 昨天 ⋅ 0

一起读书《深入浅出nodejs》-node模块机制

node 模块机制 前言 说到node,就不免得提到JavaScript。JavaScript自诞生以来,经历了工具类库、组件库、前端框架、前端应用的变迁。通过无数开发人员的努力,JavaScript不断被类聚和抽象,...

小草先森 ⋅ 昨天 ⋅ 0

Java桌球小游戏

其实算不上一个游戏,就是两张图片,不停的重画,改变ball图片的位置。一个左右直线碰撞的,一个有角度碰撞的。 左右直线碰撞 package com.bjsxt.test;import javax.swing.*;import j...

森林之下 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部