Spring源码分析 之 Spring IOC容器初始化过程(二)配置信息加载与注册

1,349 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Spring IOC容器初始化过程(二)配置信息加载与注册

上文中 Spring IOC容器初始化过程(一)资源定位过程 我们一起分析了资源定位的过程,Spring IOC 容器在初始化的过程中并不是直接读取配置文件然后进行加载,而是在开始做了许多准备工作,先初始化资源读取器,设置其读取策略,校验策略,再将数据加载至内存中,最后封装为BeanDefinition对象进行注册。

整体加载的时序图

image-20210330004112183

工程目录

image-20210329003952934

POM文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springTest</artifactId>
    <version>1.0-SNAPSHOT</version>

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>

</dependencies>
</project>

测试代码

public class ServiceB {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        Object serviceA = context.getBean("serviceA");
        System.out.println(serviceA);
    }
}

xml配置

<?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"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:component-scan base-package="com.donkeys.spring"/>
    <bean id="serviceA" class="com.donkeys.spring.service.ServiceA"></bean>
</beans>

加载过程

接上文,上文中我们已经获取到了ClassPathXmlApplicationContext的实例化对象返回的配置文件路径信息,同时调用了资源读取器的loadBeanDefinitions方法,下面从这个方法开始,正式进入到资源加载过程

XmlBeanDefinitionReader 的loadBeanDefinitions方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException;

	/**
	 * Load bean definitions from the specified XML file.
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		//判断传入的xml文件资源是否为空
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}
		//resourcesCurrentlyBeingLoaded 是一个ThreadLocal 变量,从当前线程变量获取当前的资源描述文件
		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 {
			//将资源描述文件转换为 输入流
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				//开始真正执行读取Bean定义的代码
				//从输入流中读取Spring Bean的定义
				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();
			}
		}
	}

这个方法中做了2件事

  • 判断当前资源描述文件是否重复加载
  • 如果没有重复加载,则将当前资源描述文件,转换为一个输入流,让reader进行读取

真正读取的方法是在 doLoadBeanDefinitions 中执行的

XmlBeanDefinitionReader 的doLoadBeanDefinitions 方法

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

	/**
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 * @see #doLoadDocument
	 * @see #registerBeanDefinitions
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
            //doLoadDocument方法,将输入流转换为一个文档流。同时这个方法内部,还对资源进行了校验
			Document doc = doLoadDocument(inputSource, resource);
            //开始进行注册
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}

该方法中,将输入流转换为了一个文档流,最后调用了注册方法。我们可以先看一下doLoadDocument 方法,

/**
 * Actually load the specified document using the configured DocumentLoader.
 * @param inputSource the SAX InputSource to read from
 * @param resource the resource descriptor for the XML file
 * @return the DOM Document
 * @throws Exception when thrown from the DocumentLoader
 * @see #setDocumentLoader
 * @see DocumentLoader#loadDocument
 */
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}

可以看到,这个方法的返回值直接调用了loadDocument方法,同时,还做了2件事,

  1. 先获取了实体解析器
  2. 然后获取了资源文件的校验方式,真正的校验工作是在后面进行的。我们继续往下看

XmlBeanDefinitionReader 的成员变量 documentLoader 的类为 DefaultDocumentLoader 。所以我们直接看 DefaultDocumentLoaderloadDocument 方法

/**
 * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
 * XML parser.
 */
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
      ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
	//静态工厂创建一个DocumentBuilderFactory对象,同时这个方法还会根据validationMode生成xml文件校验规则
   DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
   if (logger.isDebugEnabled()) {
      logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
   }
   //生成DocumentBuilder对象,用于解析传入的文档流
   DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
    //解析文档流,返回文档对象,解析过程中进行校验
   return builder.parse(inputSource);
}

到这里,文档被解析为了文档对象,配置文件已经完全加载到了内存中,剩下的就是解析,注册就完成了容器的初始化工作

下面回到XmlBeanDefinitionReaderdoLoadBeanDefinitions 方法,进入到注册过程

注册过程

准备进入registerBeanDefinitions 方法中

XmlBeanDefinitionReader 的registerBeanDefinitions方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException;

	/**
	 * Register the bean definitions contained in the given DOM document.
	 * Called by {@code loadBeanDefinitions}.
	 * <p>Creates a new instance of the parser class and invokes
	 * {@code registerBeanDefinitions} on it.
	 * @param doc the DOM document
	 * @param resource the resource descriptor (for context information)
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of parsing errors
	 * @see #loadBeanDefinitions
	 * @see #setDocumentReaderClass
	 * @see BeanDefinitionDocumentReader#registerBeanDefinitions
	 */
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException 	{
		//创建bean定义文档读取器用于读取给定DOM文档中包含的bean定义。
		//可以看到Spring 中分工是很明确的,刚刚读取文档的类叫做 XmlBeanDefinitionReader,他把xml文件转换为了文档对象
		//这里读取文档中bean定义的就是BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//注册前beanDefinition 的数量
		int countBefore = getRegistry().getBeanDefinitionCount();
		//解析并注册
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//注册后beanDefinition 的数量差
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

DefaultBeanDefinitionDocumentReader类

/**
 * This implementation parses bean definitions according to the "spring-beans" XSD
 * (or DTD, historically).
 * <p>Opens a DOM Document; then initializes the default settings
 * specified at the {@code <beans/>} level; then parses the contained bean definitions.
 */
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   logger.debug("Loading bean definitions");
   //获取文档根对象
   Element root = doc.getDocumentElement();
    //开始执行注册方法
   doRegisterBeanDefinitions(root);
}
/**
 * Register each bean definition within the given root {@code <beans/>} element.
 */
protected void doRegisterBeanDefinitions(Element root) {
   // Any nested <beans> elements will cause recursion in this method. In
   // order to propagate and preserve <beans> default-* attributes correctly,
   // keep track of the current (parent) delegate, which may be null. Create
   // the new (child) delegate with a reference to the parent for fallback purposes,
   // then ultimately reset this.delegate back to its original (parent) reference.
   // this behavior emulates a stack of delegates without actually necessitating one.
   /**
    * 生成一个BeanDefinitionParserDelegate 对象
    * 该类中定义了关于 spring 的xml配置文件的关键字常量配置
    * 这里又看到了分工明确的类,生成一个BeanDefinitionParserDelegate对象就是用于解析节点中Bean的定义信息的
    */
   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)) {
            if (logger.isInfoEnabled()) {
               logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }
   //在解析Bean 定义之前,进行自定义的解析,增强解析过程的可扩展性
   //留给子类实现
   /**
    * 模板设计模式,
    * 如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,
    * 那么只需要重写这两个方法就可以了
    */
   preProcessXml(root);
   //从Document的根元素进行Bean定义的Document对象
   //从根元素开始对文档进行解析,解析过程是由delegate对象完成,生成一个BeanDefinitionHolder对象,包含配置文件中所有的配置信息
   //Spring 的配置文件中,不止有bean 关键字,还有Import,Alias等关键,这里我们只讨论bean的解析
   parseBeanDefinitions(root, this.delegate);
   //解析后处理,由子类实现
   postProcessXml(root);

   this.delegate = parent;
}

可以看到DefaultBeanDefinitionDocumentReaderdoRegisterBeanDefinitions 方法中做了3件事

  1. 生成了一个用于解析文档配置的代表BeanDefinitionParserDelegate对象
  2. 配置了2个模板方法分别提供给用户用于在加载beanDefinition 之前和之后做操作
  3. 真正的解析方法在parseBeanDefinitions中执行

下面进入parseBeanDefinitions方法中

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
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);
   }
}
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//如果是import 节点,则进行导入解析
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//如果元素节点是Alias 别名元素,则进行别名解析
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//元素节点既不是导入元素,也不是别名元素,那就是普通的bean元素
		//按照Spring 的Bean 规则解析元素
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		//对beans 标签进行处理
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
/**
 * Process the given bean element, parsing the bean definition
 * and registering it with the registry.
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   //通过委托对象,解析当前元素,返回一个BeanDefinitionHolder对象,该对象中会包含我们在配置文件中给于的属性
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      //这里会判断是否该元素下有自定义的元素,如果有则将其加载进来
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         // Register the final decorated instance.
         //对bdHolder进行注册,
         //委托设计模式
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Send registration event.
      //在容器上下文中,发出现响应事件,通知相关的监听器,这个bean的相关配置已经加载完了
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

这里列出了三段代码,

第一段代码:

​ 从文档根节点开始向下遍历,依次判断该文档节点元素是默认元素还是用户自定义元素,如果是自定义元素,则进行自定义元素解析,否则按照默认元素进行解析

第二段代码:

​ 这段代码是默认元素的执行的逻辑,这里对默认元素再次进行了分类,分别分为import 标签,alias标签,bean 标签,beans标签

我们主要讨论bean标签,这几个方法都比较类似

第三段代码:

​ 这段代码主要描述了bean标签的解析过程,首先delegate根据传入的节点元素生成了BeanDefinitionHolder对象,这个对象包含了这个bean的所有配置信息。然后开始加载自定义元素,自定义元素也需要用户提供方法去解析。

最后调用了BeanDefinitionReaderUtils工具类的静态方法registerBeanDefinition,在容器中注册了这个BeanDefinitionHolder对象

下面我们继续看BeanDefinitionReaderUtils的registerBeanDefinition方法

BeanDefinitionReaderUtils的静态方法registerBeanDefinition

/**
 * Register the given bean definition with the given bean factory.
 * @param definitionHolder the bean definition including name and aliases
 * @param registry the bean factory to register with
 * @throws BeanDefinitionStoreException if registration failed
 */
public static void registerBeanDefinition(
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
      throws BeanDefinitionStoreException {

   // Register bean definition under primary name.
    //获取当前definitionHolder的名称,这个名称可以用户定义,如果用户没有定义,则spring会自动为其生成一个name,
    //name 的生成规则是 首先获取这个类的类名
    //然后判断是否是内部bean,如果是,则为 类名#hash值
    //不是内部bean ,则判断当前容器是否有同名的,没有,则为: 类名
    //有同名,设置了一个计数器,如果当前有2个同名,那么该计数器最后值为3 则最终名称为 类名#3
    //这个取名过程是在生成这个BeanDefinitionHolder对象时做的
   String beanName = definitionHolder.getBeanName();
    //开始进行注册
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

   // Register aliases for bean name, if any.
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}

BeanDefinitionRegistry接口的registerBeanDefinition方法

BeanDefinitionRegistry是一个接口,这里的主要实现类是DefaultListableBeanFactory

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {

		//判断beanName名是否为空
		Assert.hasText(beanName, "Bean name must not be empty");
		//判断beanDefinition对象是否为空
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         //注册前的最后一次校验工作,这里的校验不是校验XML文件
         //这里主要对AbstractBeanDefinition 属性的methodOverrides校验
         //校验methodOverrides是否与工厂方法并存
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }

   BeanDefinition oldBeanDefinition;
   //处理已经注册的beanName 的情况,
   oldBeanDefinition = this.beanDefinitionMap.get(beanName);
   if (oldBeanDefinition != null) {
      //如果该beanName已经注册,且在配置中配置了bean不允许被覆盖,则抛出异常
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
               "': There is already [" + oldBeanDefinition + "] bound.");
      }
      else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (this.logger.isWarnEnabled()) {
            this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  oldBeanDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(oldBeanDefinition)) {
         if (this.logger.isInfoEnabled()) {
            this.logger.info("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + oldBeanDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (this.logger.isDebugEnabled()) {
            this.logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + oldBeanDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      //所有处理完成,将该beanDefinition加入Map缓存
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            if (this.manualSingletonNames.contains(beanName)) {
               Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
               updatedSingletons.remove(beanName);
               this.manualSingletonNames = updatedSingletons;
            }
         }
      }
      else {
         // Still in startup registration phase
         this.beanDefinitionMap.put(beanName, beanDefinition);
         this.beanDefinitionNames.add(beanName);
         this.manualSingletonNames.remove(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (oldBeanDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
}

在进行为空校验之后,分别处理了2种情况

  • 容器中已经有这个beanName对象存在
    • 这里会获取配置,是否允许覆盖,如果不允许覆盖,则会抛出异常
  • 容器中没有这个beanName对象存在

这两个方法的正常流程走完都是向beanDefinitionMap中加入该bean的定义。 至此,Spring IOC容器的加载过程全部结束。配置文件中的所有bean 定义都被加入了beanDefinitionMap中