Spring IOC、AOP以及Spring-Bean循环依赖解决

文章目录

    • Spring IOC原理
      • IOC原理:
      • 依赖注入(DI)的三种方式
    • Spring-Bean循环依赖以及解决方式
      • 什么是循环依赖
      • Spring怎么解决循环依赖
        • 原理
        • Spring单例对象初始化过程
        • 三级缓存
        • Spring为什么不使用两级缓存
    • AOP
      • 使用AOP的目的
      • AOP使用场景
      • AOP原理
      • Spring AOP实现
    • 主要参考文章

Spring IOC原理

IOC原理:

IOC,即控制反转(Inversion of Control)。
在没有引入IOC容器之前,对象A如果依赖于对象B,对象A要使用对象B的时候,就必须自己去创建对象B或者使用已经创建的对象B。无论创建还是使用对象B,控制权都在对象A自己手上。

引入IOC容器后,对象A与对象B之间失去了直接联系,当对象A运行到需要使用对象B的时候,由IOC容器创建一个对象B注入到对象A中,将创建管理依赖对象的控制权转移到IOC容器中来,从而实现对象之间的解耦。

Spring IOC主要分为两个部分:

  • 初始化

在这里插入图片描述

  • 注入依赖

在这里插入图片描述

所以什么是控制反转,总结起来就是一句话,获得依赖对象的控制权由自身转移到了第三方容器上(IOC容器)

依赖注入(DI,Dependency Injection)是IOC原理的一种实现方式。在IOC容器运行期间,将依赖关系动态注入到对象之中。

依赖注入(DI)的三种方式

  • 构造方法注入
  • setter方法注入
  • 基于注解的注入

Spring-Bean循环依赖以及解决方式

什么是循环依赖

循环依赖就是两个或者两个以上的Bean互相依赖对象,形成一个闭环。
如下图,A依赖B,B依赖C,C又依赖于A,这样依赖就形成了闭环,产生了循环依赖问题。

Spring IOC、AOP以及Spring-Bean循环依赖解决_第1张图片

Spring中循环依赖的场景:

  • 构造器的循环依赖(无法解决,直接抛出BeanCurrentlyInCreationException异常)
  • Field属性的循环依赖

Spring怎么解决循环依赖

原理

基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。即原始bean对象被创建后其属性都为null,属性值可以延后设置。

Spring单例对象初始化过程

主要分三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象(bean对象属性为null)

  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

  3. initializeBean:调用spring xml中的init 方法。

Spring IOC、AOP以及Spring-Bean循环依赖解决_第2张图片

循环依赖问题主要出现在第一步(构造器循环依赖)和第二步(filed属性循环依赖)

Spring为了解决循环依赖问题,使用了三级缓存。

三级缓存

/** 单例对象的cache: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);

/** 提前曝光的单例对象的cache: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16);

/** 单例工厂的cache: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);

假设A依赖B,B依赖A

Spring在A初始化后且未注入field时,会先将原始A(属性为null)放入到singletonFactories中(预防循环依赖)。
开始注入filed时,发现A依赖B,但在三级缓存中都没有发现B。于是开始创建B。

Spring开始创建B。同样在B初始化后且未注入field时,会先将原始B(属性为null)放入到singletonFactories中。然后开始注入filed,发现B依赖A,此时在第三级缓存中发现了A,于是将A从第三级缓存singletonFactories中移动到第二级缓存earlySingletonObjects中,并且注入到B中。B完成创建放入第一级缓存singletonObjects中。于是A也完成了创建并放入到第一级缓存。

至此,循环依赖问题被解决。

Spring为什么不使用两级缓存

将三级缓存放入二级缓存的时候,会判断是否有SmartInstantiationAwareBeanPostProcessor这样的后置处理器,换句话说这里是给用户提供接口扩展的,所以采用了三级缓存。

AOP

使用AOP的目的

将那些与业务无关,却被多个业务模块所共同调用的逻辑代码封装起来,减少系统的重复代码,提高代码的可维护性。

AOP使用场景

权限控制、日志记录、异常处理、数据校验、事务管理等

AOP原理

AOP思想的实现一般是基于代理模式,主要的实现技术有Spring AOP和AspectJ

  1. AspectJ
    AspectJ采用的是静态代理,在业务代码编译时将AOP代码织入,相比Spring AOP动态代理技术,性能更好。

  2. Spring AOP
    Spring AOP采用的是动态代理,在运行期间对业务代码进行增强。

    对于动态代理技术,Spring AOP提供了对JDK动态代理的支持和CGLIB的支持。

    JDK动态代理只能为实现了接口的类创建动态代理实例。首先生成一个实现了代理接口的动态代理类,然后通过反射机制获得动态代理类的构造函数并生成一个动态代理类的实例对象,在调用具体方法前调用invokeHandler方法来处理。

    如果目标对象的实现类没有实现接口,Spring AOP将会采用CGLIB来生成AOP代理类。

Spring AOP实现

每个Spring Bean会有多个“方法拦截器”。注意:拦截器分为两层,外层由 Spring 内核控制流程,内层拦截器是用户设置,也就是 AOP。

当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法的各种信息判断该方法应该执行哪些“内层拦截器”。内层拦截器的设计就是责任链的设计。

AOP实现主要分两步:

  • 第一:代理的创建;
  • 第二:代理的调用。
  1. 代理的创建(按步骤):
    • 首先,需要创建代理工厂,代理工厂需要3个重要的信息:拦截器数组,目标对象接口数组,目标对象。

    • 创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。

    • 当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。

注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。

  1. 代理的调用

    • 当对代理对象进行调用时,就会触发外层拦截器。

    • 外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。

    • 当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。
      Spring IOC、AOP以及Spring-Bean循环依赖解决_第3张图片

主要参考文章

Spring-bean的循环依赖以及解决方式

Spring-ioc核心源码学习

Spring IOC 容器源码分析系列文章

你可能感兴趣的:(面试,Spring)