Spring的IoC容器启动过程之源码级分析

一、概述

Spring的IoC容器的启动过程,核心流程是将bean的配置项从不同渠道,包括XML、注解或者配置文件中读取和解析后,生成BeanDefinition的过程,在这过程中IoC容器会进行refresh操作,这个过程可以设置一些BeanPostProcesser的前置或后置操作,在执行完这些操作后,BeanDefinition就会被注册到BeanDefinitionRegistry容器中。

整体IoC容器的启动过程分为3个阶段:定位--->加载-->注册

  1. 定位: 通过ResourceLoader来完成资源的定位,后续的Bean配置项透视通过Resource资源文件来读取和解析的。
  2. 加载:ApplicationContext容器调用refresh()方法,解析Bean配置信息将其解析成BeanDefinition,然后调用一系列容器、Bean级别的前置后置处理器,包括调用BeanFactoryPostProcessor 的前置操作。
  3. 注册:BeanDefinition实例注册,将生成的BeanDefinition放到容器的缓存池BeanDefinitionRegistry中。

二、IoC容器介绍

首先必须要明确一点的就是BeanFactory作为所有IoC容器的顶级类,其包括哪些功能和层次结构,其中getBean(String name)是常见方法,通过该方法可以根据beanName获取Bean对象。

BeanFactory和其他IoC容器的层级关系如下。其中常见的是高级IoC容器ApplicationContext,该容器和BeanFactory相比,具有(1)能够响应时间机制;(2)具有国际化机制;

三、加载过程中常见类介绍

1.BeanDefinition

IoC容器管理的是Bean实例和其关系,在实现层面是通过BeanDefinition来进行描述的。

2.BeanDefinitionReader

BeanDefinitionReader是加载器和解析器,主要功能是从Resource资源类型中解析Bean实例出来,组装成BeanDefinition。

3.BeanDefinitionRegistry

BeanDefinition的注册表,实例化后的BeanDefinition会注册到这里。

四、源码层面分析IoC容器加载流程

IoC的的高级容器ApplicationContext有多种实现,现在以从XML中加载Bean的配置项的实例ClassPathXmlApplicationContext来具体说明IoC容器的加载流程。

1.定位阶段

通过XML配置文件启动一个ApplicationContext容器的方法。

java 复制代码
	public static void main(String[] args) {
		// 创建一个Spring容器
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
		User user1 = (User) applicationContext.getBean("user1");
		user1.sayHello();
    }

2.加载阶段

通过ClassPathXmlApplicationContext类中的 refresh()进行刷新操作。

java 复制代码
//ClassPathXmlApplicationContext.java
...
public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {

   super(parent);
   setConfigLocations(configLocations);
   if (refresh) {
      refresh();//my-刷新容器,生成BeanDefinition
   }
}
...

实现refresh()方法,其中核心是执行:obtainFreshBeanFactory() 方法,会创建一个BeanFactory的实例。同时在这个方法内也会进行一系列的初始化前或后的操作,比如执行方法:postProcessBeanFactory(beanFactory);

java 复制代码
//AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      // 这里会判断能否刷新,并且返回一个BeanFactory, 刷新不代表完全情况,主要是先执行Bean的销毁,然后重新生成一个BeanFactory,再在接下来的步骤中重新去扫描等等
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      // 准备BeanFactory
      // 1. 设置BeanFactory的类加载器、SpringEL表达式解析器、类型转化注册器
      // 2. 添加三个BeanPostProcessor,注意是具体的BeanPostProcessor实例对象
      // 3. 记录ignoreDependencyInterface
      // 4. 记录ResolvableDependency
      // 5. 添加三个单例Bean
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         // 子类来设置一下BeanFactory
         postProcessBeanFactory(beanFactory);

         StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");

         // Invoke factory processors registered as beans in the context.
         // BeanFactory准备好了之后,执行BeanFactoryPostProcessor,开始对BeanFactory进行处理
         // 默认情况下:
         // 此时beanFactory的beanDefinitionMap中有6个BeanDefinition,5个基础BeanDefinition+AppConfig的BeanDefinition
         // 而这6个中只有一个BeanFactoryPostProcessor:ConfigurationClassPostProcessor
         // 这里会执行ConfigurationClassPostProcessor进行@Component的扫描,扫描得到BeanDefinition,并注册到beanFactory中
         // 注意:扫描的过程中可能又会扫描出其他的BeanFactoryPostProcessor,那么这些BeanFactoryPostProcessor也得在这一步执行
         invokeBeanFactoryPostProcessors(beanFactory);  // scanner.scan()

         // Register bean processors that intercept bean creation.
         // 将扫描到的BeanPostProcessors实例化并排序,并添加到BeanFactory的beanPostProcessors属性中去
         registerBeanPostProcessors(beanFactory);

         beanPostProcess.end();

         // Initialize message source for this context.
         // 设置ApplicationContext的MessageSource,要么是用户设置的,要么是DelegatingMessageSource
         initMessageSource();

         // Initialize event multicaster for this context.
         // 设置ApplicationContext的applicationEventMulticaster,要么是用户设置的,要么是SimpleApplicationEventMulticaster
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         // 给子类的模板方法
         onRefresh();

         // Check for listener beans and register them.
         // 把定义的ApplicationListener的Bean对象,设置到ApplicationContext中去,并执行在此之前所发布的事件
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

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

具体执行加载BeanDefinition实例的方法。

java 复制代码
//AbstractApplicationContext.java
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   refreshBeanFactory();
   return getBeanFactory();
}

...
//AbstractRefreshableApplicationContext.java
protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);//my-加载BeanDefinition类
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
    }

需要从资源文件中读取和解析Bean的配置信息,主要通过XmlBeanDefinitionReader来实现。

java 复制代码
//AbstractXmlApplicationContext.java

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
   // Create a new XmlBeanDefinitionReader for the given BeanFactory.
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

   // Configure the bean definition reader with this context's
   // resource loading environment.
   beanDefinitionReader.setEnvironment(this.getEnvironment());
   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);//my-初始化XmlBeanDefinitionReader来
		loadBeanDefinitions(beanDefinitionReader);//my-通过XmlBeanDefinitionReader来读取BeanDefinition
}
...
  
//AbstractXmlApplicationContext.java

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
   Resource[] configResources = getConfigResources();
   if (configResources != null) {
      reader.loadBeanDefinitions(configResources);
   }
   String[] configLocations = getConfigLocations();
   if (configLocations != null) {
      reader.loadBeanDefinitions(configLocations);
   }
}

实际加载BeanDefinitions的方法。

java 复制代码
//XmlBeanDefinitionReader.java

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 currentResources = this.resourcesCurrentlyBeingLoaded.get();

   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }

   //my-从当前目录中加载bean的配置项
   try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
         inputSource.setEncoding(encodedResource.getEncoding());
      }
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException(
            "IOException parsing XML document from " + encodedResource.getResource(), ex);
   }
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   }
}

...
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.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}

		//my-从当前目录中加载bean的配置项
		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//my-实际从XML中解析Bean
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
}

3.注册阶段

开始进行注册。

java 复制代码
//XmlBeanDefinitionReader.java

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

   try {
      // spring.xml对应的Document对象
      Document doc = doLoadDocument(inputSource, resource);
      // 解析spring.xml
      int count = registerBeanDefinitions(doc, resource);
      if (logger.isDebugEnabled()) {
         logger.debug("Loaded " + count + " bean definitions from " + resource);
      }
      return count;
   }

通过建立委托类BeanDefinitionParserDelegate来进行注册。

java 复制代码
doRegisterBeanDefinitions(Element root) {
   // Any nested  elements will cause recursion in this method. In
   // order to propagate and preserve  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 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);
         // We cannot use Profiles.of(...) since profile expressions are not supported
         // in XML config. See SPR-12458 for details.
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

   preProcessXml(root);
   parseBeanDefinitions(root, this.delegate);//my-进行注册的核心方法
   postProcessXml(root);

将BeanDefinition注册到IoC容器,其底层的数据结构还是HashMap。

java 复制代码
//SimpleBeanDefinitionRegistry.java

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

   Assert.hasText(beanName, "'beanName' must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");
   this.beanDefinitionMap.put(beanName, beanDefinition);
}

4.各阶段时序图

创建ClassPathXmlApplicationContext的IoC容器类的时序图。

Spring的IoC容器启动过程之源码级分析:www.processon.com/diagraming/...

五、总结

最后总结一下,Spring IoC启动的过程就是将Bean组装成BeanDefinition保存在HashMap中的过程。

TODO

参考资料

相关推荐
sin220114 分钟前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver
kirito学长-Java1 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
海绵波波1071 小时前
flask后端开发(9):ORM模型外键+迁移ORM模型
后端·python·flask
余生H2 小时前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
绝无仅有2 小时前
PHP语言laravel框架中基于Redis的异步队列使用实践与原理
后端·面试·架构
AI人H哥会Java2 小时前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
计算机学长felix2 小时前
基于SpringBoot的“大学生社团活动平台”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
sin22012 小时前
springboot数据校验报错
spring boot·后端·python
去哪儿技术沙龙2 小时前
去哪儿机票智能预警系统-雷达系统落地实践
后端