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

简介: 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容器的方法。

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

2.加载阶段

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

//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);

//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实例的方法。

//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来实现。

//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的方法。

//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.注册阶段

开始进行注册。

//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来进行注册。

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。

//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容器启动过程之源码级分析:https://www.processon.com/diagraming/645648cb40d4bf03caee856f

五、总结

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

TODO

参考资料


目录
相关文章
|
4月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
4月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
618 5
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
547 2
|
6月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
10月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
590 70
|
9月前
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
8月前
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
735 0
|
10月前
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
421 0

热门文章

最新文章