前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详实明了的IOC容器的介绍,启动流程以及Bean的实例化和依赖注入

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

作者头像
码农飞哥
发布2021-08-18 10:46:05
3510
发布2021-08-18 10:46:05
举报
文章被收录于专栏:好好学习

前言

今天我们来认识一下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对象就相当于菜谱,告诉我们如何将原材料加工成相应的菜肴。下面我们就来看看这些比较核心的类和接口。

作用

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方法

代码语言:javascript
复制
@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方法。

代码语言:javascript
复制
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方法。

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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

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

  1. 根据传入的beanName检查beanDefinition是否存在,如果存在就是一系列的校验,主要是保证BeanDefinition的单例性,就是说IOC容器中每个Bean的实例化是单例的。
  2. 将传入的 beanDefinition实例放到 beanDefinitionMap中。
代码语言:javascript
复制
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方法 这里的 createBean方法就是我们实例化Bean的核心方法。让我们来看看。

代码语言:javascript
复制
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
                //获取BeanDefinition的实现类RootBeanDefinition实例
                RootBeanDefinition mbdToUse = mbd;
                //获取Bean的Class对象,用于后面生成实例
                Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
        if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
            mbdToUse = new RootBeanDefinition(mbd);
            mbdToUse.setBeanClass(resolvedClass);
        }
        //创造一个Bean的实例
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            if (bean != null) {
                return bean;
            }
    }

后面的调用链是 AbstractAutowireCapableBeanFactory类的 doCreateBean方法 -------> createBeanInstance方法, ------> createBeanInstance方法, -----> instantiateBean方法, -----> SimpleInstantiationStrategy类的 instantiate方法. 这个 instantiate方法就是包含了实例化Bean的真正逻辑

代码语言:javascript
复制
@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);
        }
    }

看到 instantiate方法我们就明白了,其实就是拿到 RootBeanDefinition保存的class等信息通过反射或者CCLIB的方式来生成Bean的实例。

Bean的依赖注入

说完了Bean的实例化,接下来我们来说下Bean的依赖注入。依赖注入必须用到PropertyValue类,这个类保存了Bean的所有属性和依赖信息。依赖注入的调用流程是 AbstractAutowireCapableBeanFactory类的 applyBeanPropertyValues方法调用了 applyPropertyValues方法。让我们来看看 applyPropertyValues方法。

代码语言:javascript
复制
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中。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农飞哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 项目环境
  • 核心要点
  • IOC容器的启动过程
    • 1. 资源定位,找到配置文件
      • 2.BeanDefinition的载入和解析,将配置文件解析成BeanDefiniton
        • 3. BeanDefinition的注册,将BeanDefinition向Map中注册 beanDefinitionMap
        • Bean的实例化和依赖注入
          • Bean的实例化
            • Bean的依赖注入
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档