• Spring原理:PostProcessor与AOP原理


    探究Spring原理

    注意:本版块难度很大,作为选学内容。

    如果学习Spring基本内容对你来说已经非常困难了,建议跳过此小节,直接进入MVC阶段的学习,此小节会从源码角度解释Spring的整个运行原理,对初学者来说等同于小学跨越到高中,它并不是必学内容,但是对于个人开发能力的提升极为重要(推荐完成整个SSM阶段的学习并且加以实战之后再来看此部分),如果你还是觉得自己能够跟上节奏继续深入钻研底层原理,那么现在就开始吧。

    探究IoC原理

    首先我们大致了解一下ApplicationContext的加载流程:

    我们可以看到,整个过程极为复杂,一句话肯定是无法解释的,所以我们就从ApplicationContext说起吧。

    由于Spring的源码极为复杂,因此我们不可能再像了解其他框架那样直接自底向上逐行干源码了(可以自己点开看看,代码量非常之多),我们可以先从一些最基本的接口定义开始讲起,自顶向下逐步瓦解,那么我们来看看ApplicationContext最顶层接口是什么,一直往上,我们会发现有一个AbstractApplicationContext类,我们直接右键生成一个UML类图查看:

    我们发现最顶层实际上是一个BeanFactory接口,那么我们就从这个接口开始研究起。

    我们可以看到此接口中定义了很多的行为:

     

    1. public interface BeanFactory {
    2. String FACTORY_BEAN_PREFIX = "&";
    3. Object getBean(String var1) throws BeansException;
    4. T getBean(String var1, Class var2) throws BeansException;
    5. Object getBean(String var1, Object... var2) throws BeansException;
    6. T getBean(Class var1) throws BeansException;
    7. T getBean(Class var1, Object... var2) throws BeansException;
    8. ObjectProvider getBeanProvider(Class var1);
    9. ObjectProvider getBeanProvider(ResolvableType var1);
    10. boolean containsBean(String var1);
    11. boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
    12. boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
    13. boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
    14. boolean isTypeMatch(String var1, Class var2) throws NoSuchBeanDefinitionException;
    15. @Nullable
    16. Class getType(String var1) throws NoSuchBeanDefinitionException;
    17. @Nullable
    18. Class getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
    19. String[] getAliases(String var1);
    20. }

    我们发现,其中最眼熟的就是getBean()方法了,此方法被重载了很多次,可以接受多种参数,因此,我们可以断定,一个IoC容器最基本的行为在此接口中已经被定义好了,也就是说,所有的BeanFactory实现类都应该具备容器管理Bean的基本能力,就像它的名字一样,它就是一个Bean工厂,工厂就是用来生产Bean实例对象的。

    我们可以直接找到此接口的一个抽象实现AbstractBeanFactory类,它实现了getBean()方法:

    1. public Object getBean(String name) throws BeansException {
    2. return this.doGetBean(name, (Class)null, (Object[])null, false);
    3. }

    那么我们doGetBean()接着来看方法里面干了什么:

    1. protected T doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    2. String beanName = this.transformedBeanName(name);
    3. Object sharedInstance = this.getSingleton(beanName);
    4. Object beanInstance;
    5. if (sharedInstance != null && args == null) {
    6. ...

    因为所有的Bean默认都是单例模式,对象只会存在一个,因此它会先调用父类的getSingleton()方法来直接获取单例对象,如果有的话,就可以直接拿到Bean的实例。如果没有会进入else代码块,我们接着来看,首先会进行一个判断:

    1. if (this.isPrototypeCurrentlyInCreation(beanName)) {
    2. throw new BeanCurrentlyInCreationException(beanName);
    3. }

    这是为了解决循环依赖进行的处理,比如A和B都是以原型模式进行创建,而A中需要注入B,B中需要注入A,这时就会出现A还未创建完成,就需要B,而B这时也没创建完成,因为B需要A,而A等着B,这样就只能无限循环下去了,所以就出现了循环依赖的问题(同理,一个对象,多个对象也会出现这种情况)但是在单例模式下,由于每个Bean只会创建一个实例,Spring完全有机会处理好循环依赖的问题,只需要一个正确的赋值操作实现循环即可。那么单例模式下是如何解决循环依赖问题的呢?

    我们回到getSingleton()方法中,单例模式是可以自动解决循环依赖问题的:

    1. @Nullable
    2. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    3. Object singletonObject = this.singletonObjects.get(beanName);
    4. //先从第一层列表中拿Bean实例,拿到直接返回
    5. if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
    6. //第一层拿不到,并且已经认定为处于循环状态,看看第二层有没有
    7. singletonObject = this.earlySingletonObjects.get(beanName);
    8. if (singletonObject == null && allowEarlyReference) {
    9. synchronized(this.singletonObjects) {
    10. //加锁再执行一次上述流程
    11. singletonObject = this.singletonObjects.get(beanName);
    12. if (singletonObject == null) {
    13. singletonObject = this.earlySingletonObjects.get(beanName);
    14. if (singletonObject == null) {
    15. //仍然没有获取到实例,只能从singletonFactory中获取了
    16. ObjectFactory singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
    17. if (singletonFactory != null) {
    18. singletonObject = singletonFactory.getObject();
    19. //丢进earlySingletonObjects中,下次就可以直接在第二层拿到了
    20. this.earlySingletonObjects.put(beanName, singletonObject);
    21. this.singletonFactories.remove(beanName);
    22. }
    23. }
    24. }
    25. }
    26. }
    27. }
    28. return singletonObject;
    29. }

     

    看起来很复杂,实际上它使用了三层列表的方式来处理循环依赖的问题。包括:

    • singletonObjects
    • earlySingletonObjects
    • singletonFactories

    当第一层拿不到时,会接着判断这个Bean是否处于创建状态isSingletonCurrentlyInCreation(),它会从一个Set集合中查询,这个集合中存储了已经创建但还未注入属性的实例对象,也就是说处于正在创建状态,如果说发现此Bean处于正在创建状态(一定是因为某个Bean需要注入这个Bean的实例),就可以断定它应该是出现了循环依赖的情况。

    earlySingletonObjects相当于是专门处理循环依赖的表,一般包含singletonObjects中的全部实例,如果这个里面还是没有,接着往下走,这时会从singletonFactories中获取(所有的Bean初始化完成之后都会被丢进singletonFactories,也就是只创建了,但是还没进行依赖注入的时候)在获取到后,向earlySingletonObjects中丢入此Bean的实例,并将实例从singletonFactories中移除。

    我们最后再来梳理一下流程,还是用我们刚才的例子,A依赖于B,B依赖于A:

    1. 假如A先载入,那么A首先进入了singletonFactories中,注意这时还没进行依赖注入,A中的B还是null
      • singletonFactories:A
      • earlySingletonObjects:
      • singletonObjects:
    1. 接着肯定是注入A的依赖B了,但是B还没初始化,因此现在先把B给载入了,B构造完成后也进了singletonFactories
      • singletonFactories:A,B
      • earlySingletonObjects:
      • singletonObjects:
    1. 开始为B注入依赖,发现B依赖于A,这时又得去获取A的实例,根据上面的分析,这时候A还在singletonFactories中,那么它会被丢进earlySingletonObjects,然后从singletonFactories中移除,然后返回A的实例(注意此时A中的B依赖还是null)
      • singletonFactories:B
      • earlySingletonObjects:A
      • singletonObjects:
    1. 这时B已经完成依赖注入了,因此可以直接丢进singletonObjects中
      • singletonFactories:
      • earlySingletonObjects:A
      • singletonObjects:B
    1. 然后再将B注入到A中,完成A的依赖注入,A也被丢进singletonObjects中,至此循环依赖完成,A和B完成实例创建
      • singletonFactories:
      • earlySingletonObjects:
      • singletonObjects:A,B

    经过整体过程梳理,关于Spring如何解决单例模式的循环依赖理解起来就非常简单了。

    现在让我们回到之前的地方,原型模式下如果出现循环依赖会直接抛出异常,如果不存在会接着向下:

    1. //BeanFactory存在父子关系
    2. BeanFactory parentBeanFactory = this.getParentBeanFactory();
    3. //如果存在父BeanFactory,同时当前BeanFactory没有这个Bean的定义
    4. if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
    5. //这里是因为Bean可能有别名,找最原始的那个名称
    6. String nameToLookup = this.originalBeanName(name);
    7. if (parentBeanFactory instanceof AbstractBeanFactory) {
    8. //向父BeanFactory递归查找
    9. return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
    10. }
    11. if (args != null) {
    12. //根据参数查找
    13. return parentBeanFactory.getBean(nameToLookup, args);
    14. }
    15. if (requiredType != null) {
    16. //根据类型查找
    17. return parentBeanFactory.getBean(nameToLookup, requiredType);
    18. }
    19. //各种找
    20. return parentBeanFactory.getBean(nameToLookup);
    21. }

    也就是说,BeanFactory会先看当前是否存在Bean的定义,如果没有会直接用父BeanFactory各种找。这里出现了一个新的接口BeanDefinition,既然工厂需要生产商品,那么肯定需要拿到商品的原材料以及制作配方,我们的Bean也是这样,Bean工厂需要拿到Bean的信息才可以去生成这个Bean的实例对象,而BeanDefinition就是用于存放Bean的信息的,所有的Bean信息正是从XML配置文件读取或是注解扫描后得到的。

    我们接着来看,如果此BeanFactory不存在父BeanFactory或是包含了Bean的定义,那么会接着往下走,这时只能自己创建Bean了,首先会拿到一个RootBeanDefinition对象:

     

    1. try {
    2. if (requiredType != null) {
    3. beanCreation.tag("beanType", requiredType::toString);
    4. }
    5. RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);

    下面的内容就非常复杂了,但是我们可以知道,它一定是根据对应的类型(单例、原型)进行了对应的处理,最后自行创建一个新的对象返回。一个Bean的加载流程为:

    首先拿到BeanDefinition定义,选择对应的构造方法,通过反射进行实例化,然后进行属性填充(依赖注入),完成之后再调用初始化方法(init-method),最后如果存在AOP,则会生成一个代理对象,最后返回的才是我们真正得到的Bean对象。

    最后让我们回到ApplicationContext中,实际上,它就是一个强化版的BeanFactory,在最基本的Bean管理基础上,还添加了:

    • 国际化(MessageSource)
    • 访问资源,如URL和文件(ResourceLoader)
    • 载入多个(有继承关系)上下文
    • 消息发送、响应机制(ApplicationEventPublisher)
    • AOP机制

    我们发现,无论是还是的构造方法中都会调用refresh()方法来刷新应用程序上下文:

    1. public AnnotationConfigApplicationContext(Class... componentClasses) {
    2. this();
    3. this.register(componentClasses);
    4. this.refresh();
    5. }

    此方法在讲解完AOP原理之后,再进行讲解。综上,有关IoC容器的大部分原理就讲解完毕了。

  • 相关阅读:
    【Local/Docker/K8S/Racher】Local/Docker/K8S/Racher安装Celery并启动异步任务-20220820
    2.3_9吸烟者问题
    再见 Spring Boot 1.X ,Spring Boot 2.X 走向舞台中心
    矩阵分析与应用+张贤达
    第八章 :如何基于Spring Boot +Mybatis 快速开发 Restful API
    阿里云大数据开发一面面经,已过,面试题已配答案
    写论文的步骤有没有?正确的顺序是什么?
    在Linux操作系统的ECS实例上安装Hive
    http内网穿透CYarp[开源]
    联想小新 win10电脑系统安装教程
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126441576