我该如何学习spring源码以及解析bean定义的注册

如何学习spring源码

前言

本文属于spring源码解析的系列文章之一,文章主要是介绍如何学习spring的源码,但愿可以最大限度的帮助到有须要的人。文章整体难度不大,但比较繁重,学习时必定要耐住性子坚持下去。node

获取源码

源码的获取有多种途径mysql

GitHub

spring-frameworkgit

spring-wikigithub

能够从GitHub上获取源代码,而后自行编译web

maven

使用过maven的都知道能够经过maven下载相关的源代码和相关文档,简单方便。spring

这里推荐经过maven的方式构建一个web项目。经过对实际项目的运行过程当中进行调试来学习更好。sql

如何开始学习

前置条件

若是想要开始学习spring的源码,首先要求自己对spring框架的使用基本了解。明白spring中的一些特性如ioc等。了解spring中各个模块的做用。bash

肯定目标

首先咱们要知道spring框架自己通过多年的发展示在已是一个庞大的家族。可能其中一个功能的实现依赖于多个模块多个类的相互配合,这样会致使咱们在阅读代码时难度极大。多个类之间进行跳跃很容易让咱们晕头转向。markdown

因此在阅读spring的源代码的时候不能像在JDK代码时一行一行的去理解代码,须要把有限的精力更多的分配给重要的地方。并且咱们也没有必要这样去阅读。mvc

在阅读spring某一功能的代码时应当从一个上帝视角来总览全局。只须要知道某一个功能的实现流程便可,并且幸运的是spring的代码规范较好,大多数方法基本都能见名知意,这样也省去了咱们不少的麻烦。

利用好工具

阅读代码最好在idea或者eclipse中进行,这类IDE提供的不少功能颇有帮助。

在阅读时配合spring文档更好(若是是自行编译源码直接看注释更好)。

笔记和复习

这个过程及其重要,我之前也看过一些spring的源码,可是好几回都是感受比较吃力在看过一些后就放弃了。而因为没有作笔记和没有复习的缘由很快就忘了。下次想看的时候还要从新看一遍,很是的浪费时间。

下面以IOC为例说明下我是怎么看的,供参考。

IOC

入口:ApplicationContext

在研究源码时首先要找到一个入口,这个入口怎么选择能够本身定,当必定要和你须要看的模块有关联。

好比在IOC中,首先咱们想到建立容器是在什么过程?

在程序启动的时候就建立了,并且在启动过程当中大多数的bean实例就被注入了。

那问题来了,在启动的时候是从那个类开始的呢?熟悉spring的应该都知道咱们平时在作单元测试时若是要获取bean实例,一个是经过注解,另外咱们还能够经过构建一个ApplicationContext来获取:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
	XxxService xxxService = applicationContext.getBean("xxxService");
复制代码

在实例化ApplicationContext后既能够获取bean,那么实例化的这个过程就至关于启动的过程了,因此咱们能够将ApplicationContext当成咱们的入口。

ApplicationContext是什么

首先咱们要明白的事咱们平时一直说的IOC容器在Spring中实际上指的就是ApplicationContext

若是有看过我以前手写Spring系列文章的同窗确定知道在当时文章中充当ioc容器的是BeanFactory,每当有bean须要注入时都是由BeanFactory保存,取bean实例时也是从BeanFactory中获取。

那为何如今要说ApplicationContext才是IOC容器呢?

由于在spring中BeanFactory其实是被隐藏了的。ApplicationContext是对BeanFactory的一个封装,也提供了获取bean实例等功能。由于BeanFactory自己的能力实在太强,若是可让咱们随便使用可能会对spring功能的运行形成破坏。因而就封装了一个提供查询ioc容器内容的ApplicationContext供咱们使用。

若是项目中须要用到ApplicationContext,能够直接使用spring提供的注解获取:

@Autowired
	private ApplicationContext applicationContext;
复制代码

如何使用ApplicationContext

若是咱们要使用ApplicationContext能够经过new该类的一个实例便可,定义好相应的xml文件。而后经过下面的代码便可:

@Test
    public void testClassPathXmlApplicationContext() {
        //1.准备配置文件,从当前类加载路径中获取配置文件
        //2.初始化容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application.xml");
        //二、从容器中获取Bean
        HelloApi helloApi = applicationContext.getBean("hello", HelloApi.class);
        //三、执行业务逻辑
        helloApi.sayHello();
    }
复制代码

ApplicationContext的体系

了解一个类,首先能够来看看它的继承关系来了解其先天的提供哪些功能。而后在看其自己又实现了哪些功能。

ApplicationContext继承体系

上图中继承关系从左至右简要介绍其功能。

  • ApplicationEventPublisher:提供发布监听事件的功能,接收一个监听事件实体做为参数。须要了解的能够经过这篇文章:事件监听
  • ResourcePatternResolver:用于解析一些传入的文件路径(好比ant风格的路径),而后将文件加载为resource。
  • HierarchicalBeanFactory:提供父子容器关系,保证子容器能访问父容器,父容器没法访问子容器。
  • ListableBeanFactory:继承自BeanFactory,提供访问IOC容器的方法。
  • EnvironmentCapable:获取环境变量相关的内容。
  • MessageSource:提供国际化的message的解析

配置文件的加载

Spring中每个功能都是很大的一个工程,因此在阅读时也要分为多个模块来理解。要理解IOC容器,咱们首先须要了解spring是如何加载配置文件的。

纵览大局

idea或者eclipse提供了一个很好的功能就是能在调试模式下看到整个流程的调用链。利用这个功能咱们能够直接观察到某一功能实现的总体流程,也方便在阅读代码时在不一样类切换。

以加载配置文件为例,这里给出整个调用链。

配置文件加载流程

上图中下面的红框是咱们写的代码,即就是咱们应该开始的地方。下面的红框就是加载配置文件结束的地方。中间既是总体流程的实现过程。在阅读配置文件加载的源码时咱们只须要关心这一部分的内容便可。

须要知道的是这里展现出来的仅仅只是跟这个过程密切相关的一些方法。实际上在这个过程当中还有须要的方法被执行,只不过执行完毕后方法栈弹出因此不显示在这里。不过大多数方法都是在为这个流程作准备,因此基本上咱们也不用太在乎这部份内容

refresh()

前面的关于ClassPathXmlApplicationContext的构造函数部分没有啥好说的,在构造函数中调用了一个方法AbstractApplicationContext#refresh。该方法很是重要,在建立IOC容器的过程当中该方法基本上是全程参与。主要功能为用于加载配置或这用于刷新已经加载完成的容器配置。经过该方法能够在运行过程当中动态的加入配置文件等:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
    ctx.setConfigLocation("application-temp.xml");
    ctx.refresh();
复制代码

AbstractApplicationContext#refresh

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			prepareRefresh();

			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// more statement ...
		}
	}
复制代码

这里将于当前功能不相关的部分删除掉了,能够看到进入方法后就会进入一个同步代码块。这是为了防止在同一时间有多个线程开始建立IOC容器形成重复实例化。

prepareRefresh();方法主要用于设置一些日志相关的信息,好比容器启动时间用于计算启动容器总体用时,以及设置一些变量用来标识当前容器已经被激活,后续不会再进行建立。

obtainFreshBeanFactory();方法用于获取一个BeanFactory,在这一过程当中便会加载配置文件和解析用于生成一个BeanFactory。

refreshBeanFactory

refreshBeanFactory方法有obtainFreshBeanFactory方法调用

protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
复制代码

该方法首先判断是否已经实例化好BeanFactory,若是已经实例化完成则将已经实例化好的BeanFactory销毁。

而后经过new关键字建立一个BeanFactory的实现类实例,设置好相关信息。customizeBeanFactory(beanFactory)方法用于设置是否运行当beanName重复是修改bean的名称(allowBeanDefinitionOverriding)和是否运行循环引用(allowCircularReferences)。

loadBeanDefinitions(beanFactory)方法既是开始加载bean定义的方法。当BeanFactory在加载完全部配置信息后建立,而后将建立好的BeanFactory赋值给当前context下的BeanFactory。

loadBeanDefinitions

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
复制代码

loadBeanDefinitions见名知意其就是用于加载bean定义的方法,在AbstractXmlApplicationContext中定义了一系列该方法的重载方法。上面的方法主要即是引入XmlBeanDefinitionReaderXmlBeanDefinitionReader是一个用于读取xml文件中bean定义的类,其提供了一些诸如BeanFactory和BeanDefinitionRegistery类的属性以供使用。但其实真正的读取操做并没该类完成,其也是做为一个代理存在。

在spring中若是是完成一些相似操做的类的命名都是有迹可循的,好比这里读取xml文件就是以reader结尾,相似的读取注解中bean定义也有如AnnotatedBeanDefinitionReader。若是须要向类中注入一些Spring中的bean,通常是以Aware结尾如BeanFactoryAware等。因此在阅读spring源码时若是遇到这样的类不少时候咱们能够直接根据其命名了解其大概的实现方式。

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);
					}
				}
                //logging
				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);
			}
            //logging
			return loadCount;
		}
	}
复制代码

上面代码是loadBeanDefinitions的一个实现类,该方法的主要注意点在于三个地方。

一个是方法中抛出的两个异常,前一个异常时由于ResourceLoader定义的问题,通常来讲不须要咱们关注。后一个就是配置文件出错了,多是由于文件自己xml格式出错或者是因为循环引用等缘由,具体的缘由也会经过日志打印。咱们须要对这些异常信息有印象,也不用刻意去记,遇到了能快速定位问题便可。

另外一个就是代码中的一个if(){}else{}语句块,判断语句快中都是用于解析配置文件,不一样之处在于if中支持解析匹配风格的location,好比classpath*:spring.xml这种,该功能的实现由ResourcePatternResolver提供,ResourcePatternResolverResourceLoader的功能进行了加强,支持解析ant风格等模式的location。而else中仅仅只能解析指定的某一文件如spring.xml这种。实际上在ApplicationContext中实现了ResourcePatternResolver,若是也按照spring.xml配置,也是按照ResourceLoader提供的解析方式解析。

最后一处就是Resource类,Resource是spring为了便于加载文件而特地设计的接口。其提供了大量对传入的location操做方法,支持对不一样风格的location(好比文件系统或者ClassPath)。其自己还有许多不一样的实现类,本质上是对File,URL,ClassPath等不一样方式获取location的一个整合,功能十分强大。即便咱们的项目不依赖spring,若是涉及到Resource方面的操做也可使用Spring中的Resource。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //log and assert
		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();
			}
		}
	}
复制代码

该方法依旧是loadBeanDefinitions的重载方法。

方法传入一个EncodedResource,该类能够经过制定的字符集对Resource进行编码,利于统一字符编码格式。

而后try语句块上面的代码也是比较重要的,主要功能即是判断是否有配置文件存在循环引用的问题。

循环应用问题出如今好比我加载一个配置文件application.xml,可是在该文件内部又经过import标签引用了自身。在解析到import时会加载import指定的文件。这样就形成了一个死循环,若是不解决程序就会永远启动不起来。

解决的方法也很简单,经过一个ThreadLocal记录下当前正在加载的配置文件名称(包括路径),每一次在加载新的配置文件时从ThreadLocal中取出放入到set集合中,经过set自动去重的特性判断是否循环加载了。当一个文件加载完成后,就从ThreadLocal中去掉(finally)。这里是判断xml文件时否重复加载,而在spring中判断bean是否循环引用是虽然实现上有点差异,但基本思想也是这样的。

doLoadBeanDefinitions(InputSource, Resource)

到了这一步基本上才算是真正开始解析了。该方法虽然代码行数较多,可是大多都是异常处理,异常代码已经省略。咱们须要关注的就是try中的两句代码。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}catch (Exception ex) {
			//多个catch语句块
		}
	}
复制代码

Document doc = doLoadDocument(inputSource, resource)就是读取配置文件并将其内容解析为一个Document的过程。解析xml通常来讲并不须要咱们特别的去掌握,稍微有个了解便可,spring这里使用的解析方式为Sax解析,有兴趣的能够直接搜索相关文章,这里不进行介绍。下面的registerBeanDefinitions才是咱们须要关注的地方。

registerBeanDefinitions(Document, Resource)

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.setEnvironment(getEnvironment());
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
复制代码

在进入该方法后首先建立了一个BeanDefinitionDocumentReader的实例,这和以前的用于读取xml的reader类同样,只不过该类是用于从xml文件中读取BeanDefinition

Environment

在上面的代码中给Reader设置了Environment,这里谈一下关于Environment。

Environment是对spring程序中涉及到环境有关的一个描述集合,主要分为profile和properties。

profile是一组bean定义的集合,经过profile能够指定不一样的配置文件用以在不一样的环境中,如测试环境,生产环境的配置分开。在部署时只须要配置好当前所处环境值便可按不一样分类加载不一样的配置。

profile支持xml配置和注解的方式。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    //more >

    <!-- 定义开发环境的profile -->
    <beans profile="development">
        <!-- 只扫描开发环境下使用的类 -->
        <context:component-scan base-package="com.demo.service" />
        <!-- 加载开发使用的配置文件 -->
        <util:properties id="config" location="classpath:dev/config.properties"/>
    </beans>

    <!-- 定义生产环境的profile -->
    <beans profile="produce">
        <!-- 只扫描生产环境下使用的类 -->
        <context:component-scan
            base-package="com.demo.service" />
        <!-- 加载生产使用的配置文件 -->    
        <util:properties id="config" location="classpath:produce/config.properties"/>
    </beans>
</beans>
复制代码

也能够经过注解配置:

@Service
@Profile("dev")
public class ProductRpcImpl implements ProductRpc {
    public String productBaseInfo(int id) {
        return "success";
    }
}
复制代码

而后在启动时根据传入的环境值加载相应的配置。

properties是一个很宽泛的定义,其来源不少如properties文件,JVM系统变量,系统环境变量,JNDI,servlet上下文参数,Map等。spring会读取这些配置并在environment接口中提供了方便对其进行操做的方法。

总之就是设计到跟环境有关的直接来找Environment便可。

handler

代码接着往下走,documentReader.registerBeanDefinitions(doc, createReaderContext(resource))这一步很明显就是从解析好的document对象中读取BeanDefinition的过程,可是在此以前咱们先要关注一下createReaderContext(resource)方法。

先来看一个XML文件。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc" >
</beans>
复制代码

上面是xml中根元素定义部分,可能平时并无太多人注意。其属性中的xmlns是XML NameSpace的缩写。namespace的做用主要是为了防止在xml定义的节点存在冲突的问题。好比上面声明了mvc的namespace:xmlns:mvc="http://www.springframework.org/schema/mvc"。在xml文件中咱们就可使用mvc了:

<mvc:annotation-driven />
	<mvc:default-servlet-handler/>
复制代码

而实际上在spring中还根据上面定义的namespace来准备了各自的处理类。这里由于解析过程就是将xml定义的每个节点取出根据配置好的属性和值来初始化或注册bean,为了保证代码可读性和明确的分工,每个namespace经过一个专有的handler来处理。

跟踪createReaderContext(resource)方法,最终来到DefaultNamespaceHandlerResolver类的构造方法中。

handler匹配

public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
		//DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"
		this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}
复制代码

能够看到默认的handler是经过一个本地文件来进行映射的。该文件存在于被依赖jar包下的META-INF文件夹下的spring.handlers文件中。

handler mapping

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
复制代码

这里只是展现了beans包下的映射文件,其余如aop包,context包下都有相应的映射文件。经过读取这些配置文件来映射相应的处理类。在解析xml时会根据使用的namespace前缀使用对应的handler类解析。这种实现机制其实就是所谓的SPI(Service Provider Interface),目前不少的应用都在实现过程当中使用了spi,如dubbo,mysql的jdbc实现等,有兴趣的能够取了解一下。

doRegisterBeanDefinitions(Element)

到这一步中间省略了一个方法,很简单没有分析的必要。

protected void doRegisterBeanDefinitions(Element root) {
		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);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					return;
				}
			}
		}
		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}
复制代码
delegate

这里的delegate是对BeanDefinitionParse功能的代理,提供了一些支持解析过程的方法。咱们能够看到上面有一个从新建立delegate同时又将以前的delegate保存的代码。注释上说是为了防止嵌套的beans标签递归操做致使出错,可是注释后面又说这并不须要这样处理,这个操做真的看不懂了,实际上我认为即便递归应该也是没有影响的。仍是我理解错了?

建立好delegate后下面的if语句块就是用来判断当前加载的配置文件是不是当前使用的profile指定的配置文件。上面在介绍Environment的时候已经介绍过来,若是这里加载的配置文件和profile指定的不符则直接结束。

preProcessXml(root)方法是一个空实现,而且当前spring框架中好像也没有对这个方法实现的,这里无论了。同理还有下面的postProcessXml(root)

parseBeanDefinitions(Element, BeanDefinitionParserDelegate)

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			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;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
复制代码

该方法的理解起来并不难,首先读取根节点(beans)下的全部子节点,而后对这些节点进行解析。这里须要注意的即便是对节点的解析也有一个判断语句。

主要来看一下delegate.isDefaultNamespace(ele),

public boolean isDefaultNamespace(String namespaceUri) {
        //BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"
		return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
	}
复制代码

也就是说beans命名空间下的标签一个解析方法,而另外的标签一个解析方法。

parseDefaultElement(Element, BeanDefinitionParserDelegate)

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {//import
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {//alias
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//bean
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
复制代码

能够看到对于每个标签,都提供了一个方法来进行解析,最后一个方法用于对嵌套标签进行解析,这里以bean标签的解析为例。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
		    //用于将一些属性值塞进BeanDefinition中如lazy-init
		    //以及子节点中的值 如bean节点下的property
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}
复制代码

Holder对象也是spring中的一个系列性的对象,主要就是对某一些实例进行包装,好比BeanDefinitionHolder就是对BeanDefinition进行包装,主要就是持有BeanDefinition以及它的名称和别名等(BeanDefinition为接口,没法提供名称等属性)。

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}
复制代码

接下来的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())能够算是咱们本次目标最重要的一步了,前面全部的流程都是给这一步铺垫。经过该方法将解析出来的BeanDefinition注册到容器中,方便实例化。

方法接收两个参数holder和registry,若是有看过我手写系列的文章(IOC篇)应该知道,当时为了将bean定义和容器关联起来以及为了将beanfactory的功能简化,因此咱们定义了一个BeanDefinitionRegistry接口用于将BeanDefinition注册到容器中和从容器中取BeanDefinition,这里的registry功能也是同样的。(BeanDefinitionRegistryDefaultListableBeanFactory实现,而DefaultListableBeanFactory实际就是容器)

并且能够看到实际上这里也是经过beanName来区分BeanDefinition是否重复(实际上确定是我仿的spring的(笑)),只不过为了运行名称相同的BeanDefinition注册提供了alias,以前在实现ioc时没有实现这一步。

processBeanDefinition方法的最后一步其实是注册了一个listener,在一个BeanDefinition被注册后触发,只不过上spring中实际触发方法是一个空方法,若是咱们须要在BeanDefinition注册完成后作一些什么工做能够直接继承EmptyReaderEventListener后实现componentRegistered(componentDefinition)方法便可。

到这里基本上关于BeanDefinition的加载就完成了,后面就是重复上面的流程加载多个配置文件。

小结

本节主要介绍了我关于学习spring源码的一些方法,以及以spring的BeanDefinition的加载为例分析了其总体的流程,但愿对你们能有所帮助。还要提的是spring源码很复杂,若是只是开断点一路调试下去确定是不够的,看的过程当中须要多作笔记。因为该文章内容较多以及本人水平问题,文章中可能会存在错误,若是有能够指出来方便修改。

相关文章
相关标签/搜索