spring源码分析 加载bean流程(一)

spring框架被广泛在各个项目使用,它主要的作用有 ioc,aop 等功能,为了更好的理解他的运作方式,笔者准备阅读spring ioc的源码。并把一些学习笔记和自己的一些理解和大家分享讨论。

 

话不多说,直接进入正题,我们先来简单回顾下如何使用spring的ioc功能,首先在application.xml中配置一个bean A。

<?xml version="1.0" encoding="UTF-8"?>
<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"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">
    <bean id="a" class="com.zjd.A" />

</beans>

BeanFactory beanfactory = new ClassPathApplicationContext("application.xml");
A  a = (A)beanfactory.getBean("a");
System.out.print(a);

 

以上的一段代码发生了什么,spring是如何加载一个对象到beanFactory的。自己先思考一下如何实现的,先做一个猜想

  • spring读取了xml配置文件,读取bean标签的一些属性,并封装到一个bean当中(后面知道是BeanDefinition接口,其中的实现类为RootBeanDefinition)
  • getBean(“a”)代码会触发Bean的实例化过程,并把实例化后的对象放到一个Map当中

以上猜想是否正确,我们深入源码一探究竟。

以上为ClassPathXmlApplicationContext的类结构图。AbstractApplicationContent实现了几大接口,介绍几个比较重要的接口

BeanFactory bean工厂,定义了getBean等方法。

ApplicationEventPublisher 事件发布接口

ResourceLoader 资源加载接口

 

先来看下ClassPathXmlApplicationContext 构造函数,调用了父类 AbstractApplicationContext的refresh()方法

 

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		super(parent);

		//创建解析器,解析configLocations
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}
/*
	 *	该方法是spring容器初始化的核心方法。是spring容器初始化的核心流程,是一个典型的父类模板设计模式的运用
	 *		根据不同的上下文对象,会掉到不同的上下文对象子类方法中
	 *
	 *	核心上下文子类有:
	 *	ClassPathXmlApplicationContext
	 *	FileSystemXmlApplicationContext
	 *	AnnotationConfigApplicationContext
	 *	EmbeddedWebApplicationContext(springboot)
	 *
	 
	 * */
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//为容器初始化做准备,重要程度:0
			// Prepare this context for refreshing.
			prepareRefresh();

			/**
			   重要程度:5

			
			  1、创建BeanFactory对象
			* 2、xml解析
			* 	传统标签解析:bean、import等
			* 	自定义标签解析 如:<context:component-scan base-package="com.zjd"/>
			* 	自定义标签解析流程:
			* 		a、根据当前解析标签的头信息找到对应的namespaceUri
			* 		b、加载spring所有jar中的spring.handlers文件。并建立映射关系
			* 		c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
			* 		d、调用类的init方法,init方法是注册了各种自定义标签的解析类
			* 		e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析
			*
			* 3、把解析出来的xml标签封装成BeanDefinition对象
			* */
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			/*
			* 给beanFactory设置一些属性值,可以不看
			* */
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

				postProcessBeanFactory(beanFactory);


				/*
				* BeanDefinitionRegistryPostProcessor
				* BeanFactoryPostProcessor
				* 完成对这两个接口的调用
				* */
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				/*
				* 把实现了BeanPostProcessor接口的类实例化,并且加入到BeanFactory中
				* */
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				/*
				* 国际化,重要程度2
				* */
				// Initialize message source for this context.
				initMessageSource();

				//初始化事件管理类
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				//这个方法着重理解模板设计模式,因为在springboot中,这个方法是用来做内嵌tomcat启动的
				// Initialize other special beans in specific context subclasses.
				onRefresh();

				/*
				* 往事件管理类中注册事件类
				* */
				// Check for listener beans and register them.
				registerListeners();


				/*
				* 这个方法是spring中最重要的方法,没有之一
				* 所以这个方法一定要理解要具体看
				* 1、bean实例化过程
				* 2、ioc
				* 3、注解支持
				* 4、BeanPostProcessor的执行
				* 5、Aop的入口
				*
				* */
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
		
		}
	}

以上就是spring容器启动的流程代码,具体的流程我们一步一步分析

首先看下这行代码
              
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

 

这行代码做了一些事情

  1. 创建BeanFactory对象
  2. xml解析
  3. 把解析出来的xml标签封装成BeanDefinition对象。那么BeanDefinition是个什么东东,简单理解就是存放了xml配置文件的一些属性,比如  <bean id = "a"  class = “”xxx” /> spring解析之后,会把id,class等属性封装到BeanDefinition中。后续bean实例化的时候,会读取beanDefinition的内容,并进行实例化,和属性注入等操作。

分析如何创建BeanFactory对象

先看下源码,此方法中调用的2个方法均为抽象方法

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		//核心方法,必须读,重要程度:5
		refreshBeanFactory();
		return getBeanFactory();
	}

/**
	 * Subclasses must implement this method to perform the actual configuration load.
	 * The method is invoked by {@link #refresh()} before any other initialization work.
	 * <p>A subclass will either create a new bean factory and hold a reference to it,
	 * or return a single BeanFactory instance that it holds. In the latter case, it will
	 * usually throw an IllegalStateException if refreshing the context more than once.
	 * @throws BeansException if initialization of the bean factory failed
	 * @throws IllegalStateException if already initialized and multiple refresh
	 * attempts are not supported
	 */
	protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

@Override
	public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

以下代码中我们着重分析loadBeanDefinitions(beanFactory),此方法中会读取相关xml,封装成BeanDefinition。

@Override
	protected final void refreshBeanFactory() throws BeansException {

		//如果BeanFactory不为空,则清除BeanFactory和里面的实例
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//创建DefaultListableBeanFactory

			//BeanFactory 实例工厂
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());

			//设置是否可以循环依赖 allowCircularReferences
			//是否允许使用相同名称重新注册不同的bean实现.
			customizeBeanFactory(beanFactory);

			//解析xml,并把xml中的标签封装成BeanDefinition对象
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

我们看到了一个比较重要的方法

loadBeanDefinitions(beanFactory);
            

/**
	 * Loads the bean definitions via an XmlBeanDefinitionReader.
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 * @see #initBeanDefinitionReader
	 * @see #loadBeanDefinitions
	 */
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		//创建xml的解析器,这里是一个委托模式
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());

		//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);

		//主要看这个方法  重要程度 5

		loadBeanDefinitions(beanDefinitionReader);
	}


protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//获取需要加载的xml配置文件
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

看下里面做了什么事情,首先创建了一个XmlBeanDefinitionReader ,此对象的作用主要是xml的读取。将读取xml的过程委托给了XmlBeanDefinitionReader

//XmlBeanDefinitionReader中的方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Loading XML bean definitions from " + encodedResource);
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			//获取Resource对象中的xml文件流对象
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				//InputSource是jdk中的sax xml文件解析对象
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				//主要看这个方法 **  重要程度 5
				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();
			}
		}
	}

讲了半天,我们终于进入主题doLoadBeanDefinitions,我发现spring的源码有个特征,真正干事的都是以doXXX开头的方法。

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

	
			//把inputSource 封装成Document文件对象,这是jdk的API
			Document doc = doLoadDocument(inputSource, resource);

			//主要看这个方法,根据解析出来的document对象,拿到里面的标签元素封装成BeanDefinition
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}

把inputSource封装成Document后,传入 registerBeanDefinitions方法,进行BeanDefinition的封装动作。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//又来一记委托模式,BeanDefinitionDocumentReader委托这个类进行document的解析
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		//主要看这个方法,createReaderContext(resource) XmlReaderContext上下文,封装了XmlBeanDefinitionReader对象
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  运用反射实例化BeanDefinitionDocumentReader  对象。并调用它的documentReader.registerBeanDefinitions(doc, createReaderContext(resource));方法。

下面我们进入

DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions(Element root)方法,其中root为解析到的xml文件的所有配置信息。
protected void doRegisterBeanDefinitions(Element root) {
		//省略一些不重要代码

		preProcessXml(root);

		//主要看这个方法,标签具体解析过程
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);
	}

此方法主要做了3件事情,看字面意思,1 处理xml前的准备工作。2标签解析工作。3解析之后的工作。

至此,我们分析了bean加载的大体流程,关于具体的解析工作,笔者将会在下篇文章介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值