Spring-IoC源码分析

1、IoC定义

  • 将原本在程序中⼿动创建对象的控制权,交由Spring框架来管理。
  • IoC 容器是 Spring⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象
  • 将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。
  • IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即
    可,完全不⽤考虑对象是如何被创建出来的。
  • IoC 的实现原理就是工厂模式加反射机制
  • 优点:
    • 代码量降到最低
    • 应用容易测试
    • 最小的代价和最小的侵入性使松散耦合得以实现
    • IOC容器支持加载服务时的饿汉式初始化和懒加载

2、Spring IoC容器的加载过程

主流程

  1. new AnnotationConfigApplicationContext实例化化容器入口
  2. 调用本身无参构造函数this()
    1. 会调用父类的无参构造函数创建Bean工厂,然后创建读取注解的Bean定义读取器
    2. 在创建Bean定义读取器时,会注册一些Spring内置的原始Bean定义:
      • ConfigurationClassPostProcessor: 解析我们配置类的处理器
      • AutowiredAnnotationBeanPostProcessor: 处理@Autowired注解的后置处理器
      • RequiredAnnotationBeanPostProcessor:处理@Required属性注解的后置处理器
      • 这些Spring内置的原始Bean定义通过DefaultListableBeanFactory把这些Bean定义放入BeanDefinitionMap(一个ConcurrentHashMap,beanName作为Key,beanDefinition作为Value)中以及beanDefinitionNames(一个List,里面存放了beanName)。
    3. 再创建Bean定义扫描器(仅仅是为了程序员可以手动调用)
  3. 调用register(annotatedClasses);
    1. 会循环传进来的配置类数组执行doRegisterBean方法
    2. 把当前配置类封装成Bean定义
    3. 判断是否需要跳过注解,Spring中有一个@Condition注解,当不满足条件,这个bean就不会被解析
    4. 解析Bean的作用域,如果没有设置的话,默认为单例
    5. 获得BeanName
    6. 解析通用注解,填充到Bean定义中,解析的通用注解:
      • Lazy
      • Primary
      • DependsOn
      • Role
      • Description
    7. 把Bean定义和beanName封装并调用DefaultListableBeanFactory中的registerBeanDefinition方法去注册
    8. 此时只是实例化了一个工厂、把Spring内置的原始Bean定义,还有我们传进来的Bean定义(配置类)注册到BeanDefinitionMap、beanDefinitionNames两个变量中
  4. 调用refresh();
    1. prepareRefresh()刷新前准备工作,主要是保存了容器的启动时间,启动标志等。
    2. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 拿到Bean工厂
    3. prepareBeanFactory(beanFactory); 对bean工厂进行填充属性
      • 设置了一个类加载器
      • 设置了bean表达式解析器
      • 设置了一些忽略自动装配的接口
      • 设置了一些允许自动装配的接口,并且进行了赋值操作
      • 注册bean工厂的内部的Bean
    4. invokeBeanFactoryPostProcessors(beanFactory);调用Bean工厂和调用addBeanFactoryPostProcessor添加的Bean工厂后置处理器
    5. registerBeanPostProcessors(beanFactory); 注册和实例化Bean后置处理器。
    6. finishBeanFactoryInitialization(beanFactory);
      • 实例化所有剩余的(非懒加载)单例,比如invokeBeanFactoryPostProcessors方法中根据各种注解解析出来的类,在这个时候都会被初始化。
      • 实例化的过程各种BeanPostProcessor开始起作用。

核心方法分析

refresh方法的invokeBeanFactoryPostProcessors方法
  • 实际调用的是该方法下的PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,该方法调用Bean工厂和调用addBeanFactoryPostProcessor添加的Bean工厂后置处理器
  • invokeBeanFactoryPostProcessors方法过程:
    1. 定义了一个Set,装载BeanName,根据这个Set判断后置处理器是否被执行过了
    2. 定义了两个List,一个是regularPostProcessors(装载BeanFactoryPostProcessor,即Bean工程后置处理器),一个是registryProcessors(装载BeanDefinitionRegistryPostProcessor,即Bean定义注册后置处理器)
    3. 循环传进来的postProcessor(后置处理器),只有手动add beanFactoryPostProcessor,这里才会有数据。循环会判断后置处理器是不是Bean定义注册后置处理器,是的话,执行postProcessBeanDefinitionRegistry方法,然后把对象装到registryProcessors里面去,不是的话,就装到regularPostProcessors。
    4. 定义一个List,currentRegistryProcessors保存当前准备执行方法的Bean定义注册后置处理器
    5. 找到类型为BeanDefinitionRegistryPostProcessor的后置处理器名称postProcessorNames。一般情况下,只会找到ConfigurationClassPostProcessor。
    6. 循环postProcessorNames,判断此后置处理器是否实现了PriorityOrdered接口,如果实现了,则调用getBean()的方式获取出该对象然后加入到currentRegistryProcessors集合中去,再放入processedBeans,代表这个后置处理已经被处理过了;(ConfigurationAnnotationProcessor也实现了PriorityOrdered接口)
    7. 进行排序。目前这里只有一个后置处理器,就是ConfigurationClassPostProcessor。
    8. 把保存当前准备执行方法的Bean定义注册后置处理器放入总的registryProcessors中,后续统一执行Bean工厂后置处理器接口中的方法。
    9. 调用currentRegistryProcessors中的Bean定义注册后置处理器实现的postProcessBeanDefinitionRegistry方法。(当前只有ConfigurationClassPostProcessor,此处调用的是当前只有ConfigurationClassPostProcessor实现的postProcessBeanDefinitionRegistry)
    10. 清空currentRegistryProcessors,已经完成了目前的使命,所以需要清空。
    11. 再次找到类型为BeanDefinitionRegistryPostProcessor的后置处理器名称postProcessorNames。
    12. 循环postProcessorNames,判断后置处理器是否被执行过,如果没有被执行过并且实现了Ordered接口,把此后置处理器加入到currentRegistryProcessors和processedBeans中。
    13. 进行排序。
    14. 把保存当前准备执行方法的Bean定义注册后置处理器放入总的registryProcessors中,后续统一执行Bean工厂后置处理器接口中的方法。
    15. 清空currentRegistryProcessors,已经完成了目前的使命,所以需要清空。
    16. 再次找到类型为BeanDefinitionRegistryPostProcessor的后置处理器名称postProcessorNames。
    17. 循环postProcessorNames,判断后置处理器是否被执行过,把此后置处理器加入到currentRegistryProcessors和processedBeans中。此处是把没有实现PriorityOrdered、Ordered的Bean定义注册后置处理器进行执行。
    18. 进行排序。
    19. 把保存当前准备执行方法的Bean定义注册后置处理器放入总的registryProcessors中,后续统一执行Bean工厂后置处理器接口中的方法。
    20. 清空currentRegistryProcessors,已经完成了目前的使命,所以需要清空。
    21. 执行registryProcessors中的Bean定义注册后置处理器的Bean工厂后置处理器方法。
    22. 执行regularPostProcessors中的Bean工厂后置处理器的方法。一般情况下,regularPostProcessors是不会有数据的,只有在外面手动添加BeanFactoryPostProcessor,才会有数据。
ConfigurationClassPostProcessor实现的postProcessBeanDefinitionRegistry方法
  • 实际调用的是该方法下的processConfigBeanDefinitions方法,该是真正解析Bean定义的方法。
  • ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法十分重要,主要是完成扫描,最终注册我们定义的Bean。
  • processConfigBeanDefinitions方法的过程:
    1. 获得容器中目前所有bean定义的名称放入candidateNames数组。
    2. 循环candidateNames数组,根据beanName获得Bean定义,判断此Bean定义是否解析过。
    3. 如果没有解析过,则调用ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory))进行解析并加入configCandidates候选的配置类集合中
      • 使用@Component @ComponentScan @Import @ImportResource等注解,Spring把这种配置类称之为Lite配置类(非正式的配置类), 如果加了@Configuration注解,就称之为Full配置类(正式配置类)。
    4. 若没有找到配置类 直接返回
    5. 对配置类进行排序
    6. 调用parser.parse(candidates);即ConfigurationClassParser.parse(candidates)方法,真正的解析我们的配置类
    7. 在第6步的时候,只是注册了部分Bean,像 @Bean的方法、@Import 等,是没有被注册的,调用this.reader.loadBeanDefinitions(configClasses);此处才把@Bean的方法和@Import 注册到BeanDefinitionMap中。
ConfigurationClassParser.parse方法
  1. 在该方法中,因为我们的配置类的BeanDefinition是AnnotatedBeanDefinition的实例,所以会进入第一个if,然后调用processConfigurationClass方法
  2. 在processConfigurationClass方法中重点在于doProcessConfigurationClass方法,真正的进行配置类的解析。
doProcessConfigurationClass方法
  1. 处理@PropertySource注解,@PropertySource注解用来加载properties文件。
  2. 解析我们的 @ComponentScan 注解,获得ComponentScan注解具体的内容。ComponentScan注解除了最常用的basePackage之外,还有includeFilters,excludeFilters等。
  3. 判断有没有被@ComponentScans标记,或者被@Condition条件带过,如果满足条件的话,进入if
  4. 循环解析ComponentScan注解具体的内容,进行如下操作:
    4.1 ComponentScanAnnotationParser.parse方法执行扫描操作,把扫描出来的Bean定义放入set
    4.2 循环set,判断是否是配置类,是的话,递归调用parse方法,因为被扫描出来的类,还是一个配置类,有@ComponentScans注解,或者其中有被@Bean标记的方法 等等,所以需要再次被解析。
  5. 处理@Import注解,@Import是Spring中很重要的一个注解,正是由于它的存在,让Spring非常灵活,不管是Spring内部,还是与Spring整合的第三方技术,都大量的运用了@Import注解,@Import有三种情况,一种是Import普通类,一种是Import ImportSelector,还有一种是Import ImportBeanDefinitionRegistrar,getImports(sourceClass)是获得import的内容,返回的是一个set。
  6. 处理@ImportResource注解。
  7. 处理@Bean方法,获取到我们配置类中所有标注了@Bean的方法。
ComponentScanAnnotationParser.parse方法
  1. 定义了一个扫描器scanner。
  2. 处理includeFilters,就是把规则添加到scanner。
  3. 处理excludeFilters,就是把规则添加到scanner。
  4. 解析basePackages,获得需要扫描哪些包。
  5. 添加一个默认的排除规则:排除自身。
  6. scanner.doScan执行扫描
  7. 把传进来的类似命名空间形式的字符串转换成类似类文件地址的形式,然后在前面加上classpath,即:com.xx=>classpath:com/xx/**.class。
  8. 根据packageSearchPath,获得符合要求的文件。
  9. 循环符合要求的文件,进一步进行判断。
  10. 最终会把符合要求的文件,转换为BeanDefinition,并且返回。
finishBeanFactoryInitialization方法
  1. 实际调用DefaultListableBeanFactory.freezeConfiguration方法,该方法表示注册的 bean 定义将不被修改或任何进一步的处理
  2. 实际调用DefaultListableBeanFactory.preInstantiateSingletons方法实例化剩余的单实例bean

扫描配置类并将Class注册为Bean定义流程

  1. ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry方法入口
  2. processConfigBeanDefinitions方法ConfigurationClassParser.parser进行解析Bean定义
  3. ConfigurationClassParser.doProcessConfigurationClass真正的解析我们的Bean定义
  4. ComponentScanAnnotationParser.parse方法创建扫描器
  5. ClassPathBeanDefinitionScanner.doScan方法命名空间转换为文件地址进行扫描class类并把注释了@Compent注解的class注册到Bean定义Map中

Bean定义实例化流程

  1. DefaultListableBeanFactory.preInstantiateSingletons方法入口,循环所有Bean定义
  2. getBean方法下的doGetBean是真正的获取bean的逻辑
  3. createBean方法进入创建bean的逻辑
  4. doCreateBean方法真正的创建我们的bean的实例对象的过程
  5. createBeanInstance方法创建bean实例化
  6. applyMergedBeanDefinitionPostProcessors方法调用合并Bean定义后置处理器(@AutoWired @Value的注解的预解析)
  7. populateBean方法属性赋值
  8. initializeBean进行对象初始化操作
    • applyBeanPostProcessorsBeforeInitialization方法调用Bean后置处理器的postProcessorsBeforeInitialization方法
    • invokeInitMethods方法调用初始化方法
    • applyBeanPostProcessorsAfterInitialization方法调用Bean后置处理器的PostProcessorsAfterInitialization方法

IoC源码流程总结

  1. 创建应用上下文容器

  2. this方法调用无参构造函数过程:

    1. 调用父类构造函数创建Bean工厂;
    2. 创建读取注解的Bean定义读取器,其中做了如下事情:
      1. 注册一些Spring内置的原始Bean定义(解析我们配置类的处理器、处理@Autowired、@Required注解的后置处理器、处理监听方法的注解@EventListener解析器)到Bean定义Map、BeanNamesList中
    3. 创建Bean定义扫描器(该扫描仅仅是为了提供程序员可以手动调用)
  3. 注册配置类过程:

    1. 将配置类注册到Bean定义Map、BeanNamesList中
  4. 调用refresh方法过程:

    主要是两个方法invokeBeanFactoryPostProcessors调用Bean工厂的后置处理器、finishBeanFactoryInitialization实例化Bean定义Map中未实例化的Bean

    invokeBeanFactoryPostProcessors过程(扫描配置类并将Class注册为Bean定义):

    1. 将Bean定义Map中实现了BeanDefinitionRegistryPostProcessor的类实例化并执行其实现的Bean定义注册后处理方法,此处只会有ConfigurationClassPostProcessor这个类
    2. 调用的是ConfigurationClassPostProcessor下实现的的Bean定义注册后处理方法,在此方法中会解析Bean定义
    3. 获取所有的BeanName并循环获取Bean定义进行解析
    4. 解析配置类上ComponentScans的对象集合属性进行扫描
    5. 循环包路径集合,找到包路径下找到带@Component注解的类注册到Bean定义Map中
    6. 循环上面找到的类,如果是配置类则递归扫描注册

    finishBeanFactoryInitialization过程(Bean实例化流程):

    1. 将Bean定义冻结,不能被修改或任何进一步的处理
    2. 获取所有的BeanName并循环获取尝试获取Bean,如果存在则返回
    3. 创建Bean实例,此时的Bean实例未设置属性不可用
    4. 处理合并Bean定义后置处理器,@AutoWired、@Value注解的预解析
    5. 填充属性,此时依赖注入完成
    6. 调用Bean后置处理器实现的初始化前的方法
    7. 调用初始化方法,如果实现了InitializingBean接口,调用afterPropertiesSet方法;如果定义了init-method方法,则调用init-method方法;
    8. 调用Bean后置处理器实现的初始化后的方法
    9. Bean已经被准备就绪了,一直停留在应用的上下文中

BeanFactory和FactoryBean的区别

  • BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(IOC容器)来进行管理的。FactoryBean是一个能生产或者修饰对象生成的工厂Bean

3、Spring 循环依赖问题

image-20220525101432140

  • 谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。
  • 主要解决:三级缓存解决
    • singletonObjects 一级缓存(放Object)
    • earlySingletonObjects 二级缓存(放Object)
    • singletonFactories 三级缓存(放ObjectFactory,函数接口实现回调函数)
主要流程

image-20220525101630302

  1. 一级缓存中取A对象,如果为空并且不是正在创建状态就加锁成功后再次尝试从一级缓存中获取(此处是为了防止其他线程已经创建完成),如果还是没有则进行创建;
  2. 标记A对象正在创建(放入set中),把A对象完成实例化,此时没有完成初始化;
  3. 往三级缓存中放入一个函数接口(用于其他对象依赖A对象时动态代理生成A对象并注入),此时三级缓存中有A对象;
  4. 对A对象进行属性赋值,发现要注入B对象,然后去获取B对象
  5. 从一级缓存中取B对象,如果为空并且不是正在创建状态就加锁再次尝试从一级缓存中获取(由于此时获取锁为同一线程可重入),如果还是没有则进行创建;
  6. 标记B对象正在创建(放入set中),把A对象完成实例化,此时没有完成初始化;
  7. 往三级缓存中放入一个函数接口(用于其他对象依赖A对象时动态代理生成B对象并注入),此时三级缓存中有A、B对象;
  8. 对B对象进行属性赋值,发现要注入A对象,然后去获取A对象;
  9. 此时一级缓存没有,但是A对象正在创建,然后加锁(此时加锁是为了防止其他线程获取半成品对象)从二级缓存中取,没有就从三级缓存中取回调函数调用动态代理生成对象,取到后放入二级缓存中并从三级缓存中去除,此时二级缓存中有A对象,三级缓存中有B对象;
  10. 然后把A对象注入到B对象,B对象初始化完成,撤销B对象的正在缓存标记,加锁把B放入一级缓存从二三级缓存中去除,此时二级缓存中有A对象,三级缓存中没有对象;
  11. 此时能取到B对象,然后把B对象注入A对象中,A初始化完成,把A放入一级缓存从二三级缓存;
  12. 完成A对象、B对象创建;
多线程环境下加锁原因

image-20220525101712194

  • 防止多线程情况下,一个线程在实例化Bean后,未初始化前,其他线程拿到不完成的Bean。
二级缓存的意义
  • 如果只有一级缓存,那么完整Bean和不完整Bean都放在一级缓存中,并发情况下,有可能取到半成品(未属性赋值、初始化),属性都是null。
  • 二级缓存只要是为了分离完整Bean和不完整Bean(未属性赋值、初始化)的存放, 防止多线程中在Bean还未创建完成时读取到的Bean时不完整的。所以也是为了保证我们getBean是完整最终的Bean,不会出现不完整的情况。
三级缓存的意义

为了解耦、方法职责单一、方便后期维护。

构造函数循环依赖问题
  • Spring没有解决构造函数的循环依赖
  • 解决循环依赖主要是三级缓存的实现(三个Map),在Bean调用构造器实例化之前,三级缓存(三个Map)并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
多例Bean循环依赖问题
  • 单例Bean:我们是从一级缓存中获取Bean,只需要创建一次对象,后面就可以从缓存中取出来;
  • 多例Bean:每次都要去创建对象,无法利用缓存,就无法解决循环依赖问题;

4、Spring事件

Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者

image-20220525150149314

Spring事件组成

  • 事件(ApplicationEvent) 对应于观察者模式中的被观察的事件。
  • 监听器(ApplicationListener) 对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生的响应逻辑。
  • 事件发布器(ApplicationEventMulticaster )对应于观察者模式中的被观察者/主题。负责通知观察者对外提供发布事件和增删事件监听器了。

Spring内置事件

Event说明
ContextRefreshedEvent当容器被实例化或refreshed时发布。如调用refresh()方法,此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例bean都已被实例化,所有的容器对象都已准备好可使用。
ContextStartedEvent当容器启动时发布,即调用start()方法,已启用意味着所有的Lifecycle bean都已显式接收到了start信号。
ContextStoppedEvent当容器停止时发布,即调用stop()方法,即所有的Lifecycle bean都已显式接收到了stop信号,关闭的容器可以通过start()方法重启
ContextClosedEvent当容器关闭时发布,即调用close方法。关闭意味着所有的单例bean都已被销毁,关闭的容器不能被重启或refresh。
自定义事件事件类需要继承ApplicationEvent

Spring事件机制实现流程

  1. 在创建读取注解的Bean定义读取器时会加载一些Spring原始Bean定义,其中就包括处理监听方法的注解@EventListener解析器;
  2. 在refresh方法中对Bean工厂进行填充属性时会添加一个处理实现了事件监听器接口的探测器后置处理器;
  3. 在refresh方法中创建事件多播器,如果自定义了applicationEventMulticaster 应用多播器组件,会创建自己定义的,否则创建默认的;
    自定义可以设置采用异步方式处理事件,默认采用同步方式处理事件;
  4. 注册实现了实现了事件监听器接口(ApplicationListener)监听器到多播器上,此时把非懒加载的监听器都已经注册,懒加载的监听器会在初始化后注册到多播器;
  5. 在初始化所有的Bean后就会查找所有的Bean标注了@EventListener的方法注册到多播器上。
Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐