Spring源码核心知识点凝练总结

Java
258
0
0
2023-04-18
标签   Spring

Spring源码核心知识点凝练总结

  • 全局篇
  • 个人对Spring的理解
  • IOC理解
  • DI理解
  • Spring总结概括
  • ApplicationContext与BeanFactory关系
  • 生命周期篇
  • Spring应用程序上下文生命周期
  • Bean的生命周期
  • Bean常见的作用域
  • BeanPostProcessor和BeanFactoryPostProcessor的区别
  • 依赖注入和依赖查找来源是否相同
  • ObjectFactory,FactoryBean和BeanFactory的区别
  • Setter方法产生的循环依赖如何处理
  • 持续更新中...
本文只针对Spring核心流程和AOP模块核心知识点进行凝练总结,完整的源码内容可以参考我的spring源码专栏,本文内容为作者个人观点,不一定完全正确,如果有问题,欢迎各位指出。

全局篇

站在全局的角度,一览Spring框架的架构设计。

个人对Spring的理解

  • Spring框架作为IOC容器的落地实现,提供了一个灵活的"插座",其他组件只需要简单的"插上"即可享受Spring提供的基础设施支持- ,并且结合Spring一起使用。
  • Spring的核心在于它的IOC容器设计,我们可以通过Spring应用程序上下文生命周期Spring Bean的生命周期中提供的扩展点来个性化定制IOC容器,或者插手各个Bean的创建过程,对我们感兴趣的bean进行定制化处理。
  • Spring面向模块开发的,spring大家族中各个模块小模块之间都依附于Spring IOC这个核心底层模块,各个小模块之间也不存在强耦合关系,可以随插随用。

img

IOC理解

IOC全称 Inversion Of Control ,意为控制反转,通过IOC容器,我们把对象或者组件的创建过程透明化;我们无需关注创建细节,我们只需要把创建对象,属性赋值,这些工作交给IOC容器完成即可。

在复杂的系统,对象之间的依赖关系可能是错综复杂的,在这种情况下,由于IOC容器帮助我们屏蔽了对象构造和初始化的细节,我们只需要关注对象的应用即可。

IOC容器的职责:

  • 依赖处理: 依赖查找和依赖注入
  • 托管资源(bean或其他资源)的生命周期
  • 配置管理(容器自身配置,外部化配置,托管的资源配置等)

DI理解

依赖查找和依赖注入是IOC的实现策略。

依赖查找既可以是用户主动调用IOC提供的接口进行查找,也可以IOC内部进行属性注入前,IOC自己调用进行依赖查找。依赖注入就是在依赖查找结束后,IOC容器将找到的依赖对象通过构造器,字段或者setter方法等方式注入到当前bean的属性中。

Spring总结概括

这里对Spring IOC容器做个小总结:

  • Spring框架是对IOC容器的实现,提供依赖查找和依赖注入对依赖关系进行处理,同时负责管理Bean等资源的生命周期,并在Spring应用程序上下文生命周期和Bean生命周期中提供相关扩展接口,用于针对全局粒度和bean粒度进行扩展。

ApplicationContext与BeanFactory关系

img

  • BeanFactory为我们提供了完整的IOC服务支持。
  • ApplicationContext结合使用了装饰器模式和门面模式,装饰器模式对BeanFactory进行功能增强,门面模式向用户屏蔽多个模块之间协调工作的复杂性,主要包括: 环境配置管理模块,事件发布模块,资源管理模块,国际化模块。

BeanFactory作为底层的IOC容器,ApplicationContext 将底层IOC与其他各个模块结合使用,一起构建成为Spring应用上下文。

生命周期篇

Spring应用程序上下文生命周期

Spring应用程序上下文生命周期模板过程体现在AbstractApplicationContext的refresh方法中
  • Spring应用程序上下文启动准备阶段: 设置相关属性,例如: 启动时间,状态标识,环境上下文配置对象Environment
  • BeanFactory初始化阶段: 初始一个BeanFactory对象,根据ApplicationContext子类实现不同,采用不同方式从各自支持的属性源加载Bean原数据信息,并解析为对应的BeanDefinition,注册进BeanDefinitionRegistry中。

img

  • BeanFactory准备阶段: 设置相关组件: 加载用户bean的类加载器,默认为线程上下文类加载器(可打破双亲委派机制);表达式语言解析器;属性编辑器;添加相关后置处理器和需要忽略依赖注入的相关接口配置。
  • BeanFactory后置处理阶段: 在beanFactory实例化并准备完毕后,允许子类覆写该空回调接口,对IOC容器进行一些后置处理,如: 添加一些BeanPostProcessor。 web环境下的applicationContext子类会覆写此方法针对web环境进行一些特殊配置、
  • 调用BeanFactoryPostProcessors相关回调接口: 主要是执行BeanDefinitionRegistry和BeanFactoryPostProcessor相关后置处理回调接口,先调用用户手动设置,再调用从容器中获取的,这其他还涉及到优先级顺序,具体可以参考这部分源码。通常使用BeanFactoryPostProcessor往容器中注入额外一些BeanDefinition。
  • 注册BeanPostProcessor阶段: 从BeanDefinition集合中查询出所有类型为BeanPostProcessor的bean,并进行提前初始化,然后放入BeanFactory的BeanPostProcessor集合中集合管理。
  • 初始化相关内建Bean: 初始化MessageSource用于国际化的Bean,ApplicationEventMulticaster事件多播器对象,ThemeSource对象。
  • OnRefresh回调接口: web环境下的ApplicationContext会重写该钩子方法,用于在此刻启动web服务器。
  • 事件监听器注册: 获取BeanDefinition集合中所有类型为ApplicationListener的事件监听器,然后注册到事件多播器中。
  • BeanFactory初始化完成阶段: 核心是初始化所有Bean(除了部分提前已经初始化好的,如: 相关后置处理器),当然还要排除那些抽象bean,非单例bean,懒加载的bean。
  • 所有bean初始化完成阶段: 在所有bean(非抽象,非单例,非懒加载)初始化后,Spring会再次遍历所有初始化好的单例bean对象,如果当前bean是SmartInitializingSingleton类型,则调用其afterSingletonsInstantiated回调方法。
  • Spring应用上下文刷新阶段: 清除当前Spring应用上下文中的缓存,例如: 通过ASM扫描处理的元数据。发布上下文刷新事件。

Bean的生命周期

Bean的生命周期模板过程体现在AbstractBeanFactory的createBean方法中
  • Bean配置和扫描注册阶段:
  • 元信息配置阶段: 面向资源(xml,properties) ,面向注解或者面向API(配置类)进行配置
  • 元信息解析阶段: 将元信息统一解析为BeanDefinition对象,该对象包含定义Bean的所有信息,并且采用不同方式加载的bean,会对应不同的BeanDefinition实现。具体参考: BeanDefinition体系结构
  • 元信息注册阶段: 将BeanDefinition配置原信息保存到BeanDefinitionRegistry中。
  • bean实例化阶段(省略缓存检查和bean提前暴露等阶段):
  • BeanDefinition合并阶段: 定义的bean可能存在父子关系,需要进行属性合并,存在相同配置则覆盖父属性,并且不同来源的bean,采用不同BeanDefinition进行存储,这里需要统一转换为RootBeanDefintion。
  • 实例化阶段: 从BeanDefinition中获取bean的全类名,从ClasUtils中获取默认的线程上下文类加载器,利用线程上下文类加载器去加载用户的bean,然后实例化出一个bean实例对象。这个过程涉及构造器注入,实例化前后两个扩展点:
InstantiationAwareBeanPostProcessor:
- postProcessBeforeInstantiation 方法
- postProcessAfterInstantiation 方法
  • Bean属性赋值阶段:
  • 属性赋值阶段: 将实例化完成的bean包装为BeanWrapper,并利用BeanWrapper完成setter方式的依赖注入。依赖注入前首先需要获取该对象所有属性与属性值的映射关系,也就是PropertyValues,其中一部分可能是我们通过配置文件指定的,在元信息解析阶段就已经放入BeanDefinition中,这部分属性借助BeanWrapper完成依赖注入。还有一部分是通过注解方式指定的,需要通过相关BeanPostProcessor解析各自支持的注解完成依赖注入:
属性依赖注入过程中涉及到的核心扩展接口:
InstantiationAwareBeanPostProcessor:
- postProcessProperties 方法

涉及到的常见bean后置处理器有:
- CommonAnnotationBeanPostProcessor(@Resource@PostConstruct@PreDestroy)
- AutowiredAnnotationBeanPostProcessor(@Autowired@Value)

注意: 如果我们既在配置文件中声明了属性依赖注入的配置,又通过注解形式指定了依赖注入配置,那么最终只会执行一次依赖注入,具体源码为:
InjectedElement类中的inject方法,相关bean后置处理器会调用该方法完成最终的属性注入,该方法在进行最终注入前,
会调用checkPropertySkipping方法判断我们是否已经通过配置文件指明了依赖关系,如果是跳过注入,交由BeanWrapper完成最终的依赖注入
  • Bean初始化阶段:
  • Aware接口回调阶段: 如果当前bean实现了相关Aware接口(例如: BeanNameAware,ApplicationContextAware),这里会通过回调接口完成依赖注入。
  • 初始化方法调用阶段: 调用当前bean配置了相关初始化方法,如: @PostConstruct标注方法,实现InitiallizingBean接口的afterPropertiesSet方法,和自定义初始化方法。在调用初始化方法前后,还提供了两个扩展点:
BeanPostProcessor:
- postProcessBeforeInitialization
- postProcessAfterInitialization
  • Bean初始化完成阶段:
  • 当前bean初始化完毕后,还会进行循环依赖检查,判断是否出现提前暴露的bean和最终放入容器bean不一致的问题,主要是因为提前暴露的bean没有进行代理,而最终注入容器中的bean是被代理过的。
  • 为当前bean注册相关销毁方法,如: @PreDestory标注的方法,DisposableBean接口提供的destroy方法,destroy-method自定义的销毁方法。

Bean常见的作用域

  • singleton: 默认作用域,一个BeanFactory只有一个bean实例
  • prototype: 原型作用域,每次依赖查找和依赖注入都生成新的bean对象
  • request: 将Spring Bean存储在ServletRequest上下文中
  • session: 将Spring Bean存储在HttpSession中
  • application: 将Spring Bean存储在ServletContext中

BeanPostProcessor和BeanFactoryPostProcessor的区别

  • 工作时机不同
  • BeanFactoryPostProcessor工作时机: Spring应用程序上下文生命周期中对初始化完毕的BeanFactory进行后置处理
  • BeanPostProcessor工作时机: 每个Bean的生命周期涉及到的相关生命周期回调接口
  • 作用不同
  • BeanFactoryPostProcessor主要用于给初始化完毕的BeanFactory中添加一些额外的BeanDefinition
  • BeanPostProcessor主要用于在每个Bean的生命周期回调接口中进行拦截,针对自己感兴趣的bean进行定制化处理
  • 调用者不同:
  • BeanFactoryPostProcessor只能由ApplicationContext进行调用
  • BeanPostProcessor保存于BeanFactory中,由BeanFactory进行调用

依赖注入和依赖查找来源是否相同

  • 依赖查找(getBean)的来源仅限于BeanDefinition集合和单例对象集合
  • 依赖注入的来源还包括Resolvable Dependency,即Spring应用上下文定义的可以处理的注入对象,以及@Value所标注的外部化配置

img

ObjectFactory,FactoryBean和BeanFactory的区别

  • ObjectFactory可关联某一类型的bean,通过提供一个getObject方法返回目标bean对象。在Spring中ObjectFactory最出名的应用莫过于延迟依赖查找
  • 通过该特性,Spring处理setter方法产生的循环依赖时,可以在某个bean实例化完毕后,先缓存一个ObjectFactory对象(调用getObject方法可返回当前正在初始化的Bean对象),如果初始化过程中依赖的对象又依赖于当前Bean,会先通过缓存的ObjectFactory对象获取当前正在初始化的Bean,这样一来就解决了setter方法产生的循环依赖问题。
  • FactoryBean也可以关联一个Bean对象,通过getObject方法返回目标bean对象。FactoryBean对象在被依赖注入或依赖查找时,得到的Bean是通过getObject方法返回的目标对象。如果我们想要获取FactoryBean这个对象本身,需要在beanName前面加上&。
  • 我们可以通过FactoryBean实现复杂的初始化逻辑,例如: 在Spring集成MyBaits项目中,会为每个Mapper接口生成一个MapperFactoryBean对象,当我们注入Mapper接口时,实际上就是通过调用MapperFactoryBean的getObject方法返回一个代理对象,关于数据库的操作都是通过该代理对象完成的。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  @Override
  public boolean isSingleton() {
    return true;
  }
  ...
}
  • BeanFactory是Spring底层的IOC容器,里面保存了所有单例Bean,负责提供完整的IOC服务支持。

Setter方法产生的循环依赖如何处理

循环依赖是指Bean字段注入出现的循环依赖,构造器注入产生的循环依赖对于Spring来说无法自动解决,可以通过延迟初始化来处理,并且Spring只解决单例模式下的循环依赖。

Spring解决循环依赖主要借助于3个Map集合:

  • singletonObjects (一级缓存) : 里面保存了所有已经初始化好的单例Bean
  • earlySingletonObjects(二级缓存): 里面保存从三级缓存中获取到的正在初始化的Bean
  • singletonFactories(三级缓存): 里面保存了正在初始化的Bean对应的ObjectFactory,通过调用ObjectFactory的getObject方法,我们能够获取到正在初始化的Bean对象,然后将其放入二级缓存中,并从三级缓存移除。

为什么需要三级缓存,二级缓存不就能够解决setter造成的循环依赖问题了吗?

  • 三级缓存中的ObjectFactory的getObject方法中会调用SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(…) 方法尝试对当前bean进行代理,然后返回一个代理对象,确保提前暴露的bean如果需要被代理,也是被代理过的。
  • 对于不需要代理的Bean,singletonFactories三级缓存确实没必要,但是AOP是Spring体系中核心一员,如果没有singletonFactories三级缓存,意味着Bean在实例化后就需要完成AOP代理,这违背了Spring的设计原则。
  • Spring是通过AbstractAutoProxyCreator这个自动代理创建器在Bean完成属性注入和初始化方法调用后,才会对bean尝试进行代理,而不是实例化后里面进行AOP代理。
  • 如果出现了循环依赖,那么只有给Bean先创建代理,但是在没有出现循环依赖的情况下,设计之初就是让Bean在完成创建好后才进行AOP代理。

如果产生了循环依赖,那么自动代理创建器的getEarlyBeanReference方法中,会对bean尝试进行代理,并进行标记 ,在postProcessAfterInitialization方法中发现getEarlyBeanReference方法已经被调用过,那么此时就会跳过代理尝试。

@Transactional注解底层就是借助自动代理创建器完成的对象代理所以不存在循环依赖问题,但是@Async注解底层使用的是AsyncAnnotationBeanPostProcessor后置处理器,没有实现getEarlyReference方法,所以在循环依赖场景下,会报错。

img