• SpringIOC的原理以及源码


    为什么要设计模式

    首先我想讲一个比较抽象的概念,没有在实际项目上写过代码的小伙伴可能不知道设计模式有什么用。

    首先,我有一只笔

    public class Pen {
    
        public void draw() {
            System.out.println("draw");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后有一个画家用了这只笔

    public class Painter {
        
        public void usePen() {
            Pen pen = new Pen();
            pen.paint();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这段代码在正常情况下没有什么问题,但是,在企业生产环境中往往有很多画家要用这只笔。现在我有10000个类,Painter1,Painter2…Painter10000,他们都调用了这个笔的paint方法

    这时候,这支笔突然换路径了。10000个画家都找不到这只笔了,如果想要正常使用这只笔,必须将10000个画家的调用路径都修改一遍。如果这么做的话程序员也不用写代码了,天天在这为画家换笔

    再比如,这支笔的paint方法换成了write方法,等等等等,像这种问题我们称之为强依赖(强耦合),10000个画家都依赖与这只笔

    我们需要一定的方式去解决强耦合的问题,这种方式被称之为设计模式,设计模式的目的是让程序中的某个类发生改变是其他的类不用发生巨大修改,比如下面的工厂模式

    public class PenFactory {
        
        public Pen getPen() {
            return new Pen();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我现在有一个工厂去造这支笔,所有的画家都从这个工厂的getPen中获得笔,现在笔的路径改了,对我们10000个画家都不会产生什么影响,我们只要在工厂中修改一次笔的路径就行了。这样画家和笔产生了解耦

    下面的IOC中无时无刻不体现了这种思想

    依赖倒置原则

    (依赖倒置原则仅作了解)

    IOC根据依赖倒置原则演变而来,那依赖倒置原则解决了什么问题呢?

    如果是面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本

    底层是上层的组件,依赖倒置原则是面向接口编程的一种思路。一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化

    依赖倒置原则是设计模式的六大原则之一,简单来说就是:针对接口编程,依赖于抽象而不依赖于具体

    IOC(控制反转)与DI(依赖注入)

    为什么要有IOC呢?

    试想一下,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,也就是对各个类的控制权在我们手中,这样的话我们需要手动的去创建各个类并且维持他们的依赖关系,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合

    而解决问题的方案就是IOC。既然我们手动去操作非常麻烦,我们可以写一段代码去操作这些依赖关系啊

    IOC核心概念就是将你设计好的对象以及他们的依赖关系交给第三方容器控制。IOC容器和DI让上层从被动接受一个底层组件,变成主动选择一个底层组件,这样我们就不需要去思考这些对象的依赖关系,使系统耦合度降低

    而所谓的依赖注入,就是把底层类作为参数传入上层类、将依赖项注入到被依赖项中,实现上层类对下层类的“控制”。此时就体现出依赖抽象的好处了:我们可以注入任何抽象类的子类,让程序变的更加多样性

    IOC容器

    计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。这句话在设计模式中体现的淋漓尽致,任何强耦合的对象在他们之间加一次中间件就能解耦,IOC容器就是负责解耦的容器,你可以把它看成是一个装对象的集合

    IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖

    IOC容器有两个好处:一是容器管理所有的对象,我们不需要去手动创建对象;二是容器管理所有的依赖项,在创建实例的时候不需要了解其中的细节

    而Spring对IOC容器的具体实现就是BeanFactory

    SpringIOC的大体思路

    首先IOC容器需要知道这个类的具体位置才能找到对应的对象,而我们就用xml或者注解的方式标记这个类的位置

    然后通过反射以获得该类的对象(反射的作用是可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性)

    IOC容器中存放了程序需要的所有的对象,这些对象只会被创建一次(单例),容器将这些对象包装成一个个bean(带有对象信息以及该对象的结构体),IOC容器简单来说不过是一个以该对象名称为key、对应bean为value的map

    IOC容器完成创建这一步之后会根据我们所需要的方式来管理bean的依赖

    源码

    来看看IOC容器是如何实现上面的过程的

    最常用的IOC容器是ClassPathXmlApplicationContext,我们就从它开始,它的类继承图如下
    在这里插入图片描述
    不要害怕,比较重要的接口也只有几个

    BeanFactory接口定义了容器的最基本功能,它可以读取类的配置文档,管理类的加载,实例化,维护类之间的依赖关系。实现了此接口的类才能叫容器(实现它的容器实例化时并不会初始化配置文件中定义的类,初始化动作发生在第一次调用时)

    ApplicationContext接口除了提供容器的基本功能外还提供了很多的扩展功能(实现它的容器实例化时就会将配置文件中定义的类初始化)

    其他的比较重要的接口:

    ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的

    ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系

    AutowireCapableBeanFactory 就是用来自动装配 Bean 用的,上图并没有显示它。但是不继承不代表不可以使用组合

    容器创建

    从创建bean工厂开始看看它的过程

    需要重点关注的点是:refresh重建工厂,bean定义,别名处理,bean覆盖

    refresh

    	//这一行我们开始创建bean工厂
    	ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    	//最终都会调用这个方法来创建工厂
        public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
            super(parent);
            //读取配置
            this.setConfigLocations(configLocations);
            if (refresh) {
            	//主要方法,调用这个方法创建容器,这个方法可以手动调用
                this.refresh();
            }
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    this.refresh()其实调用了它父类AbstractApplicationContext的refresh方法

    有没有想过这个方法为什么叫refresh而不叫create?

    因为容器建立以后,我们可以通过调用refresh()方法重建,refresh()会将原来的容器销毁,然后再重新执行一次初始化操作,因此命名为refresh而不是init或者其他名字

        public void refresh() throws BeansException, IllegalStateException {
        	// 该操作是上锁的,保证线程安全
            synchronized(this.startupShutdownMonitor) {
                this.prepareRefresh();
                // 创建bean工厂,如果有一个在运行的工厂,先销毁再创建一个
                ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
                // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中
                this.prepareBeanFactory(beanFactory);
    
                try {
                	//bean的初始化过程
                    this.postProcessBeanFactory(beanFactory);
                    this.invokeBeanFactoryPostProcessors(beanFactory);
                    this.registerBeanPostProcessors(beanFactory);
                    this.initMessageSource();
                    this.initApplicationEventMulticaster();
                    this.onRefresh();
                    this.registerListeners();
                    //初始化所有单例bean
                    this.finishBeanFactoryInitialization(beanFactory);
                    this.finishRefresh();
                } catch (BeansException var9) {
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                    }
    
                    this.destroyBeans();
                    this.cancelRefresh(var9);
                    throw var9;
                } finally {
                    this.resetCommonCaches();
                }
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    	  //这一步的重点是以下两个方法
          //设置BeanFactory的两个配置属性:是否允许 Bean 覆盖、是否允许循环引用
          customizeBeanFactory(beanFactory);
    
          //加载Bean到BeanFactory中
          loadBeanDefinitions(beanFactory);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    BeanDefinition

    这个接口非常重要,配置文件被Springxml解析后,这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们配置的一个个类的信息都会存放在一个个BeanDefinition中

    public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
        String SCOPE_SINGLETON = "singleton";
        String SCOPE_PROTOTYPE = "prototype";
        int ROLE_APPLICATION = 0;
        .....
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注册Bean

    读取完配置之后,会以beanname为键,beanfefinition为值,存放在一个map中,这个过程叫注册bean

       //调用此函数开始注册
       registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
    • 1
    • 2

    别名处理:
    使用map保持别名,遇到别名时,先把别名变成beanName

       String[] aliases = definitionHolder.getAliases();
       if (aliases != null) {
          for (String alias : aliases) {
             registry.registerAlias(beanName, alias);
          }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    bean覆盖:指多个bean的名字相同,默认情况下spring支持bean覆盖,在注册bean遇到bean覆盖时会这样处理

       BeanDefinition oldBeanDefinition;
       oldBeanDefinition = this.beanDefinitionMap.get(beanName);
       if (oldBeanDefinition != null) {
          if (!isAllowBeanDefinitionOverriding()) {
             // 如果不允许覆盖的话,抛异常
             throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription()...
          }
          else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
             // log...用框架定义的 Bean 覆盖用户自定义的 Bean 
          }
          else if (!beanDefinition.equals(oldBeanDefinition)) {
             // log...用新的 Bean 覆盖旧的 Bean
          }
          else {
             // log...用同等的 Bean 覆盖旧的 Bean,这里指的是 equals 方法返回 true 的 Bean
          }
          // 覆盖
          this.beanDefinitionMap.put(beanName, beanDefinition);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    完成注册的是这些语句,会有两个集合保存信息

             // 将 BeanDefinition 放到这个 map 中,这个 map 保存了所有的 BeanDefinition
             this.beanDefinitionMap.put(beanName, beanDefinition);
             // 这是个 ArrayList,所以会按照 bean 配置的顺序保存每一个注册的 Bean 的名字
             this.beanDefinitionNames.add(beanName);
    
    • 1
    • 2
    • 3
    • 4

    bean创建

    需要关注的点是:两次循环依赖、作用域判断、对象创建、实例化时CGlib处理,并且有很多步骤都是认证这个bean是否合法

    getBean

    在以上这些结束后,Spring遍历map集合,将所有的bean初始化,初始化的过程封装到了getBean方法里

    getBean应该是我们获取bean使用的方法,所以这个方法有一层判断,如果在单例池中存在这个bean,直接返回这个bean

       final String beanName = transformedBeanName(name);
       Object bean; 
       Object sharedInstance = getSingleton(beanName);
       if (sharedInstance != null && args == null) {
       	   ......
       	   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来是一大串bean认证环节(认证这个bean是否合法)

    在认证的过程中有一步,需要先初始化依赖的bean,此时初始化的是depends-on中定义的依赖,而不是它调用的依赖,因此必定不支持循环依赖

    以下过程,判断该对象依赖是否为空,如果有依赖,使用isDependent方法判断是否循环依赖,如果是,抛异常

             String[] dependsOn = mbd.getDependsOn();
             if (dependsOn != null) {
                for (String dep : dependsOn) {
                   if (isDependent(beanName, dep)) {
                      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                   }
                   registerDependentBean(dep, beanName);
                   getBean(dep);
                }
             }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后进行bean作用域的判断,不管这个bean的作用域是什么(单例原型其他),总是要进入createBean方法中,createBean进行一些认证又会转入doCreateBean中

    实例化bean

          instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    • 1

    进入此方法,又是一段认证过程,然后实例化时分为很多情况,比如:有参构造创建对象、无参构造、存在方法覆写时使用CGLIB代理创建对象,没有就使用java反射创建对象

    循环依赖

    只有set注入才能解循环依赖,构造器注入不行,因为set在对象被实例化之后才进行DI,在实例化与DI之间有操作空间,如果在构造器是解单例的循环依赖可能就要在JVM上操作操作了

    此时,容器会创建三个缓存,表示此bean是否正在创建,这些缓存主要用来解决循环依赖

    • 一级缓存 : Map singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例

    • 二级缓存 : Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例,但是并没有进行DI

    • 三级缓存 : Map> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象

    解决循环依赖,首先判断这个bean是否允许循环依赖(单例,true),获取缓存并判断

       boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
             isSingletonCurrentlyInCreation(beanName));
       if (earlySingletonExposure) {
          if (logger.isDebugEnabled()) {
             logger.debug("Eagerly caching bean '" + beanName +
                   "' to allow for resolving potential circular references");
          }
          addSingletonFactory(beanName, new ObjectFactory<Object>() {
             @Override
             public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
             }
          });
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    具体判断逻辑是这样的:

    1,创建A实例,实例化的时候把A对象⼯⼚放⼊三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道

    2,A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B

    3,同样,B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存

    4,接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存

    那么问题来了,为什么要三级缓存?如果只实现这个功能的话二级缓存不是够了吗?

    主要是为了⽣成代理对象,因为三级缓存中放的是⽣成具体对象的匿名内部类,获取Object的时候,它可以⽣成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象

    处理完循环依赖后,设值,DI

          populateBean(beanName, mbd, instanceWrapper);
    
    • 1

    DI之后,这已经是一个完整的java对象了,不过还不是一个完整的bean,它还需要进行初始化initializeBean,也就是bean生命周期的一部分

  • 相关阅读:
    在进行自动化测试,遇到验证码的问题,怎么办?
    怎么把录音转文字?快把这些方法收好
    技术博客一件发布系统的实验性技术方案Butterfly
    打通“隔墙”!浅析低代码超强的整合能力
    Node学习笔记之fs模块
    【Python】正则表达式及re模块
    Kafka (二) ---------- Kafka 快速入门
    正则表达式——2.正则表达式的基础
    COMSOL空气反应 模型框架
    卷积神经网络
  • 原文地址:https://blog.csdn.net/sekever/article/details/126052816