详实明了的IOC容器的介绍,启动流程以及Bean的实例化和依赖注入

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 今天我们来认识一下Spring IOC容器,本文主要介绍SpringIOC容器的核心要点以及其启动流程和实例化流程。

前言

今天我们来认识一下Spring IOC容器,本文主要介绍SpringIOC容器的核心要点以及其启动流程和实例化流程。

项目环境

Springframework 4.3.12

核心要点

Spring IOC是什么?他有什么作用呢?我们通过了解学习,Spring IOC是一个容器,用于生成和管理Bean的实例,以及实例之间的依赖关系,然后注入相关的依赖。这里我们可以把IOC容器想象成一个餐馆。我们去餐馆点菜的话,不需要关心菜的生成过程,不需要关心菜的原材料从哪里来。我们只需要最终做好的菜。这里的菜就是我们的需要的Bean。不同的菜对应不同的Bean。没有IOC 容器的情况下,如果需要一个Bean的话,就需要自己来new一个对象的实例,比如A类依赖了B类,那么就需要在A类中new一个B类的实例对象,这就好像我们要自己在家动手做菜一样。有了IOC容器之后,如果A类依赖B类,只需要通过IOC容器帮我们创建A类的实例和B类的实例,然后IOC容器会将B类的实例注入到A类中。这就很像餐馆把菜做好之后送给我们一样。既然IOC容器这么6,那么他是如何实现这一切的呢?

还是回到餐馆那个例子,做菜的话就需要与原材料和菜谱,同样的IOC容器想要管理各个业务对象以及他们之间的依赖关系,就需要通过某种途径来记录和管理这些信息,而BeanDefinition对象就承担了这个责任。IOC容器中每一个Bean都会有一个对应的BeanDefinition实例,该实例负责保存bean对象的所有必要信息,包括Bean对象的class类型,是否是抽象类,构造方法和参数,以及其他属性等,这里的BeanDefinition就相当于原材料。而BeanDefinitionRegistry对象和BeanFactory对象就相当于菜谱,告诉我们如何将原材料加工成相应的菜肴。

下面我们就来看看这些比较核心的类和接口。

ce79b1a1d7642893dde65da8cf6e6abb_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png


作用
BeanFactory BeanFactory接口主要包括了getBean,containsBean,getAllases等方法,作用是操作Bean
BeanDefinitionRegistry BeanDefinitionRegistry接口抽象出了Bean的注册逻辑,其主要包括了registerBeanDefinition,removeBeanDefinition,getBeanDefinition等方法
ConfigurableListableBeanFactory ConfigurableListableBeanFactory接口包括了getBeanDefinition等方法,可以获取BeanDefinition实例
DefaultListableBeanFactory DefaultListableBeanFactory类同时实现了ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,说明其承担了Bean的注册和管理工作
BeanDefinition BeanDefinition接口是用来存储Bean对象的必要信息,包括Bean对象的class类型,是否是抽象类,构造方法和参数,依赖关系,以及其他属性等
PropertyValue 这个类就是具体存放Bean对象的属性信息以及其依赖信息

认识上面的几个核心接口和类,对我们下面看Bean的启动过程和实例化过程有很大的帮助。


需要说明的是,在Spring中,ApplicationContext是IOC容器的承载体,而BeanFactory是操作这个容器的工具,两者关系紧密,相互协作,refresh方法实现了ApplicationContext和BeanFactory相互协作的过程,不同之处主要在于子类 AbstractRefreshableApplicationContext 和 GenericApplicationContext 中实现,两者使用的 BeanFactory 都为 DefaultListableBeanFactory,它构建在BeanFactory之 上,属于更⾼级的容器,除了具有BeanFactory的所有能⼒之外,还提供对事件监听机制以及国际化的⽀持等。它管理的bean,在容器启动 时全部完成初始化和依赖注⼊操作。

IOC容器的启动过程

介绍完了IOC容器的核心类和要点,接下来我们看看IOC容器的启动过程,其启动过程主要有如下三个步骤:

1. 资源定位,找到配置文件

这里定位资源有两种方式,一种是通过ClassPathXmlApplicationContext类来解析Spring的配置文件的形式,就是通过配置文件来定义Bean的情况,另外,一种情况就是通过注解的方式来定义Bean的情况,这种情况是通过AnnotationConfigApplicationContext类解析的,主要是扫描项目的classPath下定义的注解。下面我们首先介绍下通过ClassPathXmlApplicationContext。这个类的核心作用是作为一个解析Xml的入口,其调用链是: ClassPathXmlApplicationContext类的构造器

------>AbstractApplicationContext类的refresh方法

----->调用AbstractRefreshableApplicationContext类的refreshBeanFactory方法

---->XmlWebApplicationContext类的loadBeanDefinitions方法

----> AbstractBeanDefinitionReader类的loadBeanDefinitions方法

---->XmlBeanDefinitionReader类的loadBeanDefinitions方法

---->XmlBeanDefinitionReader类的doLoadBeanDefinitions方法

---->XmlBeanDefinitionReader类的registerBeanDefinitions方法

---->DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法

调用层次很深,我们就直接跳到核心的方法来看。下面我们就来看看registerBeanDefinitions方法

@Override
  public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
  this.readerContext = readerContext;
  logger.debug("Loading bean definitions");
  //读取XML文件
  Element root = doc.getDocumentElement();
  //载入并注册BeanDefinition
  doRegisterBeanDefinitions(root);
  }

然后,registerBeanDefinitions方法只是读取到根节点root之后,就另外一个核心方法doRegisterBeanDefinitions方法,然后,doRegisterBeanDefinitions方法又把逻辑转给了parseBeanDefinitions方法,这个parseBeanDefinitions方法首先获取所有的子节点 ,然后遍历解析子节点,载入BeanDefinition又交给了parseDefaultElement方法和parseCustomElement方法。

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

2.BeanDefinition的载入和解析,将配置文件解析成BeanDefiniton

说完了配置文件的解析之后,接下来,我们来看看BeanDefinition的载入和解析。我们直接找到parseDefaultElement方法。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //省略部分非核心代码
  //如果节点是bean节点,说明是一个Bean
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
  }
  }

这个方法按照节点名,调用不同的处理方法,在此处我们只看节点为bean时调用的方法processBeanDefinition方法。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  if (bdHolder != null) {
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    try {
    // Register the final decorated instance.(注册BeanDefinition)
    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    }
    catch (BeanDefinitionStoreException ex) {
    getReaderContext().error("Failed to register bean definition with name '" +
      bdHolder.getBeanName() + "'", ele, ex);
    }
    // Send registration event.
    getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
  }
  }

我们重点看BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());这个方法,这个方法才是真正的将传入BeanDefinitionRegistry类,载入并解析BeanDefinition,然后对BeanDefinition进行注册。

3. BeanDefinition的注册,将BeanDefinition向Map中注册beanDefinitionMap

接下来就到了我们的重头戏,注册BeanDefinition到beanDefinitionMap中,其中key就是Bean的id,其中beanDefinitionMap是一个定义在DefaultListableBeanFactory类中全局的线程安全的map,用于存放解析到的BeanDefinition。

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

让我们来看看registerBeanDefinition方法吧,这个方法核心的步骤有两步:

1.根据传入的beanName检查beanDefinition是否存在,如果存在就是一系列的校验,主要是保证BeanDefinition的单例性,就是说IOC容器中每个Bean的实例时单例的。

2.将传入的beanDefinition实例放到beanDefinitionMap中。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    throws BeanDefinitionStoreException {
    if (hasBeanCreationStarted()) {
    // Cannot modify startup-time collection elements anymore (for stable iteration)
    //加锁,保证线程安全
    synchronized (this.beanDefinitionMap) {
    // 将beanDefinition值设置到beanDefinitionMap中
      this.beanDefinitionMap.put(beanName, beanDefinition);
      List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
      updatedDefinitions.addAll(this.beanDefinitionNames);
      updatedDefinitions.add(beanName);
      this.beanDefinitionNames = updatedDefinitions;
      if (this.manualSingletonNames.contains(beanName)) {
      Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
      updatedSingletons.remove(beanName);
      this.manualSingletonNames = updatedSingletons;
      }
    }
    }
    else {
    // Still in startup registration phase,将beanDefinition值设置到beanDefinitionMap中
    this.beanDefinitionMap.put(beanName, beanDefinition);
    this.beanDefinitionNames.add(beanName);
    this.manualSingletonNames.remove(beanName);
    }
    this.frozenBeanDefinitionNames = null;
    }

小结,至此,我们对IOC容器的初始化过程就解析完了,其实其初始化过程还是比较简单的,只是Spring的代码结构比较深,核心代码不好找。

Bean的实例化和依赖注入

说完了IOC容器的初始化过程,接下来,我们来看看IOC容器的实例化过程。经过上一个阶段,所有Bean定义都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求通过容器的getBean方法请求某个对象,或者因为依赖关系容器需要隐式的调用getBean方法时,就会触发第二阶段的活动,IOC容器首先检查所请求的对象是否已经实例化完成,如果没有,则会根据注册的BeanDefinition所提供的信息实例化请求对象。并为其注入依赖,当该对象装配完成后,容器会立即返回给请求方法。

Bean的实例化

让我们从前面提到的getBean方法说起,这里的调用链如下:

AbstractBeanFactory类的getBean方法

----->AbstractBeanFactory类的doGetBean方法

----->AbstractBeanFactory类的createBean方法

------>AbstractAutowireCapableBeanFactory类的doCreateBean方法(这个方法主要是)

创建Bean的核心逻辑就在AbstractAutowireCapableBeanFactory类的doCreateBean方法中:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
  // Instantiate the bean(实例化Bean)
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    //如果是单例Bean,首先清理FactoryBean缓存
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    //使用特定的策略实例化Bean,如果工厂方法、构造器等,将BeanDefinition转换为BeanWrapper
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
  Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
  // Allow post-processors to modify the merged bean definition. 允许MergedBeanDefinitionPostProcessor修改BeanDefinition
   synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }
  // Eagerly cache singletons to be able to resolve circular references  
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  //需要提前暴露Bean的条件:单例&&允许循环依赖&&当前Bean正在创建中,检测到了循环依赖
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                         "' to allow for resolving potential circular references");
        }
        //对于需要提前暴露的Bean,以其ObjectFactory的形式放入singletonFactories中,以解决循环依赖的问题
        //ObjectFactory所创建的Bean由getEarlyBeanReference()方法指定
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
  // Initialize the bean instance. Bean的初始化
  Object exposedObject = bean;
    try {
        //对Bean进行属性的填充。此外,如果依赖了其他Bean,则会在这里注入依赖
        populateBean(beanName, mbd, instanceWrapper);
        //执行Bean的初始化方法,如配置的init-method等
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
  //循环依赖检查
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    //省略部分代码
      throw new BeanCurrentlyInCreationException();
                }
            }
        }
    }
    try {
        //注册Bean的销毁方法,如destroy-method
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }
    return exposedObject;
}
}

从doCreateBean方法我们可以看出Bean生命周期的一个完整过程,这个方法里面的流程很长,知识点很多。下面我们就对流程进行具体的分析。

流程分析

实例化Bean

实例化Bean的逻辑是在SimpleInstantiationStrategy类的instantiate方法中实现的。Bean的实例化其实就是将指定的BeanDefinition转换成BeanWrapper,然后通过指定构造器和默认无参构造器,CGLB动态代理等方式来实例化Bean,这时实例化后的Bean是一个刚实例化好的,属性未赋值的空Bean。

@Override
  public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
  //省略部分代码
  // Don't override the class with CGLIB if no overrides.
  if (bd.getMethodOverrides().isEmpty()) {
    //通过构造器实例化Bean  
    return BeanUtils.instantiateClass(constructorToUse);
  }
  else {
    // Must generate CGLIB subclass.(通过CGLIB的方式生成Bean)
    return instantiateWithMethodInjection(bd, beanName, owner);
  }
  }

Bean的依赖注入(属性注入)

说完了Bean的实例化,接下来我们来说下Bean的依赖注入。属性注入必须用到PropertyValue类,这个类保存了Bean的所有属性和依赖信息。

依赖注入的调用流程是AbstractAutowireCapableBeanFactory类的applyPropertyValues方法。

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
    //类型转换接口
    TypeConverter converter = getCustomTypeConverter();
  if (converter == null) {
    converter = bw;
  }
  BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
  String propertyName = pv.getName();
                  //对于ref来说就是beanName,对于value 来说就是value
    Object originalValue = pv.getValue();
    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
    boolean convertible = bw.isWritableProperty(propertyName) &&
      !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
    if (convertible) {
      convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
    }
  try {
    //设置依赖属性
    bw.setPropertyValues(new MutablePropertyValues(deepCopy));
  }
  catch (BeansException ex) {
    throw new BeanCreationException(
      mbd.getResourceDescription(), beanName, "Error setting property values", ex);
  }
  }
  private Object convertForProperty(Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
  if (converter instanceof BeanWrapperImpl) {
    return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
  }
  else {
    PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
    MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
    return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
  }
  }

其中TypeConverter 类型转化接口,将传入的值转化为其需要的类型。

SimpleTypeCoverter 是TypeConverter接口的一个实现。其依赖于java.beans中的PropertyEditor,其类似于java GUI中的编程,例如:拖拽一个button, 然后,设置其颜色,长度,宽度,这些都属于button的属性,在java.beans中将这些抽象成了一个PropertyEditor 接口。 setAsText(), 例如button 的高度,值是什么跟属性的类型密切相关。

总结

本文主要介绍了IOC容器的核心概念,以及其启动过程。然后,就是介绍了Bean的实例化过程,熟悉IOC容器我们需要先了解清楚其核心的几个接口,例如:BeanFactory接口,BeanDefinitionRegistry接口等。IOC容器的启动过程无非就是解析配置文件,将属性值存放到BeanDefinition中。Bean的实例化是通过反射或者CGLIB的方式来的。Bean中的属性是存放在PropertyValue中。


相关文章
|
14天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
52 6
|
30天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
34 1
|
3月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
4月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
166 3
|
3月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
86 0
|
5月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
5月前
|
人工智能 Kubernetes 开发者
容器化技术在AI开发流程中的应用
【8月更文第17天】随着人工智能(AI)技术的快速发展,如何高效地开发、测试和部署AI模型成为了一个重要的课题。容器化技术,如Docker和Kubernetes,为解决这一问题提供了强大的工具。本文将探讨这些技术如何简化AI应用程序的开发流程,并提高模型的可移植性和可扩展性。
286 0
|
25天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
184 77
|
1月前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序
|
5天前
|
Ubuntu Linux 开发工具
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包成标准化单元(容器),确保在任何支持 Docker 的操作系统上一致运行。容器共享主机内核,提供轻量级、高效的执行环境。本文介绍如何在 Ubuntu 上安装 Docker,并通过简单步骤验证安装成功。后续文章将探讨使用 Docker 部署开源项目。优雅草央千澈 源、安装 Docker 包、验证安装 - 适用场景:开发、测试、生产环境 通过以上步骤,您可以在 Ubuntu 系统上成功安装并运行 Docker,为后续的应用部署打下基础。
docker 是什么?docker初认识之如何部署docker-优雅草后续将会把产品发布部署至docker容器中-因此会出相关系列文章-优雅草央千澈