`

Spring源码学习-XML 配置方式的IoC容器启动过程分析

阅读更多

以FileSystemXmlApplicationContext为例,把Spring IoC容器的初始化流程走一遍:
ApplicationContext context = new FileSystemXmlApplicationContext
            ("C:/Users/ZARA/workspace/HelloSpring/src/Beans.xml");

<beans>
	<import resource="classpath:config/spring/that-other-xml-conf.xml"/>
	<alias name="trianglePoint" alias="triangle-alias"/>
	<bean id="yourCoolBean" class="org.jdong.MyCoolBean">
		<property name="age">100</property>
	</bean>
</beans>

整个流程可用一句话简单概括:
读入并解析XML文件,把文件中Bean的定义保存在Map里

相应的要做以下几件事:
1.定位Resource
2.读入Resource,解析并创建BeanDefinition
3.把BeanDefinition注册到Map里

第1步由DefaultResourceLoader实现(FileSystemXmlApplicationContext是DefaultResourceLoader的子类)
第2步交给了BeanDefinitionReader,而BeanDefinitionReader又委托给BeanDefinitionParserDelegate
注意这一步创建的不是Bean的实例,Bean的实例化是在整个配置文件解析完毕之后再进行
第3步比较简单,交给BeanDefinitionReaderUtils


首先要明确,FileSystemXmlApplicationContext是实现了ResourceLoader、ResourcePatternResolver接口,继承了DefaultResourceLoader:
利用org.apache.commons.lang.ClassUtils可以查看FileSystemXmlApplicationContext都实现了哪些接口,都继承了哪些类
allInterfaces:
interface org.springframework.beans.factory.BeanNameAware
interface org.springframework.beans.factory.InitializingBean
interface org.springframework.context.ConfigurableApplicationContext
interface org.springframework.context.ApplicationContext
interface org.springframework.beans.factory.ListableBeanFactory
interface org.springframework.beans.factory.BeanFactory
interface org.springframework.beans.factory.HierarchicalBeanFactory
interface org.springframework.context.MessageSource
interface org.springframework.context.ApplicationEventPublisher
interface org.springframework.core.io.support.ResourcePatternResolver
interface org.springframework.core.io.ResourceLoader
interface org.springframework.context.Lifecycle
interface org.springframework.beans.factory.DisposableBean
allSuperclasses:
class org.springframework.context.support.AbstractXmlApplicationContext
class org.springframework.context.support.AbstractRefreshableConfigApplicationContext
class org.springframework.context.support.AbstractRefreshableApplicationContext
class org.springframework.context.support.AbstractApplicationContext
class org.springframework.core.io.DefaultResourceLoader
class java.lang.Object

因此查看AbstractXmlApplicationContext就不会迷惑了:
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		//ResourceLoader是this,因为AbstractXmlApplicationContext实现了ResourceLoader接口
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}


由于Spring支持太多的配置方式了,阅读源码时跳来跳去,容易迷失
因此,最好是根据上面要做的几件事,一个一个的去找

1.资源定位
DefaultResourceLoader的getResource方法显示,有三种形式的Resource:
	public Resource getResource(String location) {
		if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		else {
			try {
				URL url = new URL(location);
				return new UrlResource(url);
			}
			catch (MalformedURLException ex) {
				return getResourceByPath(location);
			}
		}
	}

FileSystemXmlApplicationContext是第三种:
	protected Resource getResourceByPath(String path) {
		if (path != null && path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}

DefaultResourceLoader.getResource(string)在哪被调用呢?
在AbstractBeanDefinitionReader:
public int loadBeanDefinitions(String location, Set actualResources)  {
		ResourceLoader resourceLoader = getResourceLoader();

		if (resourceLoader instanceof ResourcePatternResolver) {
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				return loadCount;
			}
		}
	}

OK,Resource找到了

2.读取XML并创建BeanDefinition
XmlBeanDefinitionReader(Spring是用DOM的方式来解析XML的):
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			int validationMode = getValidationModeForResource(resource);
			Document doc = this.documentLoader.loadDocument(
					inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
			return registerBeanDefinitions(doc, resource);
		}
	
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	}

顺着上面的方法一直看下去(我们只看XML Namespace为default的那部分)
到了DefaultBeanDefinitionDocumentReader:
	//遍历XML root里的每个Element(通常是一个Bean定义)
	NodeList nl = root.getChildNodes();
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		if (node instanceof Element) {
			Element ele = (Element) node;
				parseDefaultElement(ele, delegate);
		}
	}
				
	//三部分,import, alias, bean我们主要看bean那部分
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
	}
	
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		//读取bean配置
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		//注册到Map
		BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
	}

接着去到了BeanDefinitionParserDelegate,这个类承担了大部分的“脏活”:
	//从下面代码中可见,如果定义了id,则beanName=id,否则取name列表中的第一个为beanName。除了beanName外,其他称为alias
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		List<String> aliases = new ArrayList<String>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
		}

		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}

	public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, BeanDefinition containingBean) {

		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);

		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		parseMetaElements(ele, bd);
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
		parseConstructorArgElements(ele, bd);
		
		//主要看一下这个方法
		parsePropertyElements(ele, bd);
		parseQualifierElements(ele, bd);

		return bd;
	}
	
	//可能有多个Property
	public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			parsePropertyElement((Element) node, bd);
		}
	}
	
	public void parsePropertyElement(Element ele, BeanDefinition bd) {
			Object val = parsePropertyValue(ele, bd, propertyName);
			PropertyValue pv = new PropertyValue(propertyName, val);
			parseMetaElements(ele, pv);
			pv.setSource(extractSource(ele));
			bd.getPropertyValues().addPropertyValue(pv);
	}
	
	public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {

		/*Property有多种形式,例如
		<property name="message" value="Hello World!"/>  
        <property name="age">  
            <value>1</value>  
        </property>  
		也可以是List, Map等等
		*/
		// Should only have one child element: ref, value, list, etc.
		NodeList nl = ele.getChildNodes();
		Element subElement = null;
		for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				subElement = (Element) node;
			}
		}

		boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
		boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);

		//ref="..."
		if (hasRefAttribute) {
			String refName = ele.getAttribute(REF_ATTRIBUTE);
			RuntimeBeanReference ref = new RuntimeBeanReference(refName);
			ref.setSource(extractSource(ele));
			return ref;
		}
		//value="..."
		else if (hasValueAttribute) {
			TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
			valueHolder.setSource(extractSource(ele));
			return valueHolder;
		}
		//非直接量,例如List
		else if (subElement != null) {
			return parsePropertySubElement(subElement, bd);
		}
	}
	
	//这个方法涵盖了List, Map, Array等等
	public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) {
	    if (nodeNameEquals(ele, REF_ELEMENT)) {
			// A generic reference to any name of any bean.
			String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE);
			boolean toParent = false;
			if (!StringUtils.hasLength(refName)) {
				// A reference to the id of another bean in the same XML file.
				refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE);
				if (!StringUtils.hasLength(refName)) {
					// A reference to the id of another bean in a parent context.
					refName = ele.getAttribute(PARENT_REF_ATTRIBUTE);
					toParent = true;
					if (!StringUtils.hasLength(refName)) {
						error("'bean', 'local' or 'parent' is required for <ref> element", ele);
						return null;
					}
				}
			}
			if (!StringUtils.hasText(refName)) {
				error("<ref> element contains empty target attribute", ele);
				return null;
			}
			RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);
			ref.setSource(extractSource(ele));
			return ref;
		}
		else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
			return parseIdRefElement(ele);
		}
		else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
			return parseValueElement(ele, defaultValueType);
		}
		else if (nodeNameEquals(ele, NULL_ELEMENT)) {
			// It's a distinguished null value. Let's wrap it in a TypedStringValue
			// object in order to preserve the source location.
			TypedStringValue nullHolder = new TypedStringValue(null);
			nullHolder.setSource(extractSource(ele));
			return nullHolder;
		}
		else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
			return parseArrayElement(ele, bd);
		}
		else if (nodeNameEquals(ele, LIST_ELEMENT)) {
			return parseListElement(ele, bd);
		}
		else if (nodeNameEquals(ele, SET_ELEMENT)) {
			return parseSetElement(ele, bd);
		}
		else if (nodeNameEquals(ele, MAP_ELEMENT)) {
			return parseMapElement(ele, bd);
		}
		else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
			return parsePropsElement(ele);
		}
		else {
			error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
			return null;
		}
	}
	


Spring IoC容器启动过程是相当复杂,因为它支持各种灵活的配置——这可能是一个框架所必须的吧
但就我个人而言,在可能的情况下,我会限定只采用一两种配置(例如XML),从而简化逻辑,所谓“约定大于配置”

参考文章:http://www.ibm.com/developerworks/cn/java/j-lo-beanxml/
参考文章里面把流程做成了图片,方便我们把握整个脉络
0
0
分享到:
评论

相关推荐

    官方原版源码spring-framework-5.1.4.RELEASE.zip

    例如,关于IoC(Inversion of Control,控制反转)的实现,Spring使用了XML配置或注解来定义bean的依赖关系,通过反射机制动态加载和管理bean。另外,AOP模块实现了切面编程,允许我们定义横切关注点,如日志、事务...

    spring-framework-master

    例如,Spring的事件驱动模型、AOP的实现原理、以及IoC容器的内部工作流程等。此外,源码还展示了Spring如何与其他技术(如JDBC、JMS、EJB等)无缝集成,以及如何利用注解简化配置。 通过阅读和分析"spring-...

    Spring的IoC容器初始化源码解析

    ### Spring的IoC容器初始化源码解析 #### 一、Spring框架的核心——IoC容器 Spring框架是一个开源的轻量级Java开发框架,其核心功能是IoC(Inversion of Control,控制反转)容器和AOP(Aspect Oriented ...

    官方源码spring-framework-5.0.15.RELEASE.zip

    在源码中,我们可以看到`org.springframework.beans`和`org.springframework.context`包,它们是IoC容器的基础,通过BeanFactory和ApplicationContext实现对象的创建、配置和管理。 2. AOP支持:Spring的AOP模块...

    spring源码spring-framework-4.3.2.RELEASE

    通过XML配置或注解方式定义bean,IoC容器负责实例化、装配和管理这些bean。 2. **AOP(Aspect Oriented Programming)**:Spring的AOP模块实现了面向切面编程,允许开发者定义方法拦截器和切入点表达式,用于代码的...

    spring源码解读-地址.txt

    根据提供的文件信息,本次解读将围绕Spring框架的核心概念与源码分析进行展开。Spring框架作为Java企业级开发中不可或缺的一部分,其源码的学习对于深入...希望以上内容能够为大家学习Spring源码提供一定的参考和帮助。

    官方原版源码 spring-framework-5.1.15.RELEASE.zip

    Spring Core负责基础的IoC(Inversion of Control)容器,Spring Beans则实现了Bean的生命周期管理和配置,Spring Context则是基于Core之上构建的,提供了一个应用上下文,可以管理Bean并与其他Spring模块集成。...

    Spring IoC源码深度剖析开源架构源码2021.pdf

    文档可能将深入探讨Spring IoC容器初始化、Bean生命周期管理、依赖注入等关键概念,并以2021年的开源版本为背景进行分析。 从提供的部分文档内容来看,我们可以提炼出以下几个知识点: 1. **BeanFactory与...

    官方原版源码spring-framework-5.2.3.RELEASE.zip

    深入学习Spring源码,不仅能够帮助我们理解其工作原理,还能提升我们在实际项目中的问题排查能力。例如,当我们遇到Spring的依赖注入问题时,可以通过查看`BeanFactory`和`ApplicationContext`的相关源码来定位问题...

    官方原版源码spring-framework-4.3.22.RELEASE.zip

    通过XML配置、注解或Java配置,可以实现对象的创建和依赖注入。 2. **AOP**:Spring的AOP模块提供了面向切面编程的能力,允许开发者定义“切面”,在运行时动态地将代码切入到应用程序的特定连接点上,如方法调用、...

    spring源码spring-framework-4.2.5.RELEASE

    1. **IoC(Inversion of Control)容器**:Spring的核心组件,负责管理对象的生命周期和依赖关系。在`org.springframework.beans`和`org.springframework.context`包中,我们可以看到BeanFactory和...

    官方原版完整包 spring-framework-5.1.18.RELEASE.zip

    IoC容器负责管理对象的生命周期和依赖关系,而AOP则提供了一种模块化处理横切关注点的方式。在数据访问方面,Spring支持JDBC、ORM(Object-Relational Mapping)框架如Hibernate和MyBatis,以及对NoSQL数据库的支持...

    spring-framework-1.0-m1.zip源码

    XML配置是Spring 1.0中的主要配置方式,通过元素定义Bean,通过属性指定类路径、构造参数、依赖关系等。例如: ```xml ``` 五、未来的发展 Spring 1.0的发布为后续版本奠定了坚实的基础。随着版本的升级,...

    官方源码 spring-framework-5.3.3.zip

    Spring的IoC容器是其核心功能之一,通过XML或注解配置,它可以管理对象的生命周期和依赖关系。在源码中,`BeanFactory`和`ApplicationContext`接口定义了容器的基本操作,如`getBean`、`refresh`等。`...

    官方原版源码spring-framework-5.2.1.RELEASE.zip

    IoC容器是Spring的核心,它负责管理对象的生命周期和依赖关系。AOP则为应用程序提供了一种模块化和解耦的方式,可以方便地实现横切关注点,如日志、事务管理等。 在Spring 5.2.1中,源码的改进主要集中在以下几个...

    官方原版源码spring-framework-5.1.13.RELEASE.zip

    - **spring-framework-5.1.13.RELEASE-schema.zip**:这个文件包含Spring框架的XML配置文件的架构定义,这对于理解如何通过XML配置来驱动Spring容器的行为至关重要。 4. **关键模块解析** - **Core Container**...

    spring-01-ioc1.rar

    2. **Spring容器**:Spring的核心是IoC容器,如BeanFactory和ApplicationContext,它们负责读取配置,实例化、配置及管理Bean。 3. **Bean定义**:如何在XML或Java配置中定义Bean,包括其类名、初始化方法、属性...

    官方原版源码 spring-framework-5.0.18.RELEASE.zip

    深入学习Spring源码,不仅可以提高我们的开发效率,还能使我们更好地应对复杂的业务场景。通过对源码的分析,我们可以了解Spring如何实现IoC、AOP等高级特性,以及它如何优化性能和提高可扩展性。这将对提升我们的...

    官方原版源码 spring-framework-5.2.9.RELEASE.zip

    1. **设计模式实践**:Spring源码中大量运用了工厂模式、单例模式、观察者模式等设计模式,学习源码能加深对设计模式的理解。 2. **性能优化**:通过对源码的学习,开发者可以了解Spring如何进行性能优化,如缓存、...

    Spring IOC容器实现分析

    本文将基于Spring 2.0版本的源码,深入分析Spring IOC容器的内部实现机制,帮助读者更好地理解和使用这一强大的工具。 #### 二、核心概念与术语 在深入探讨Spring IOC容器之前,我们首先需要了解几个核心的概念: ...

Global site tag (gtag.js) - Google Analytics