spring循环依赖问题

搞清楚什么是循环依赖?

  • 依赖:引用、成员变量
    ClassA类---->ClassB类 ClassB类—>ClassA类
  • 依赖注入有两种注入方式:构造方法\setter方法注入
  • 循环依赖分为:
    构造方法
    setter方法

循环依赖的例子

class OrderService{
	UserService userService;
			
	saveOrder(){
	//插入订单表(需要用户名称,而页面只传递一个用户ID)
	//调用UserService去查询用户信息
	}
}
class UserService{
	OrderService orderService;
			
	queryOrders(){
	//调用OrderService的服务
	}
}

以上伪代码可造成‘死锁’。

spring中Bean实例的完整创建流程

一、Bean的实例化(new)

--------此处会反射调用构造器去new对象,所以说此处有可能会发生【构造器循环依赖】

  • 此处产生的【构造器循环依赖】无法自动解决,只能修改依赖关系或者改为setter方法去依赖
class OrderService{
	UserService userService;
				
	public OrderService(UserService userService){
		this.userService=userService;
	}
				
	saveOrder(){
		//插入订单表(需要用户名称,而页面只传递一个用户ID)
		//调用UserService去查询用户信息
	}
}

注意:

  • 此时从Java本身来说,Bean实例对象已经new 出来了,可以正常使用了。
  • 但是从Spring来说,该Bean必须完成三个步骤之后,才认为可以被人正常使用

二、Bean的属性填充(setter)

--------此处会对成员变量调用setter方法去进行依赖注入。----此处可能会发生【setter方法循环依赖】

  • 其实此时Bean实例已经存在了,只是没有对外暴露使用而已。
  • Spring的单例Bean被创建成功之后,会放入SingletonObjects的Map集合中,对外暴露使用。
  • 可以使用缓存的方式去解决循环依赖问题。

例如以下循环依赖

ClassA{
	ClassB
}								
ClassB{
	ClassA
}

二级缓存和三级缓存都是为了处理正在创建中的Bean这个场景的。

A—B B—C C—A
A—C 如果是以上循环依赖时,就需要二级缓存的作用

创建ClassA的流程

1.ClassA实例化

  • 如果是单例bean,并且允许引用、该beanname存储到singletonsCurrentlyInCreation(Set集合),那么ClassA对象会提前被暴露给三级缓存保存,这样三级缓存才能访问到。

2.ClassA属性填充—给ClassB类型变量赋值(getBean)

  • 2.1 ClassB实例化
  • 2.2 ClassB属性填充—ClassA类型变量赋值(getBean—去ioc容器中找)
  • 如果此时还找不到ClassA,那么又会从第一步开始了,那这样就成死循环了。
  • 为了不让死循环,那么我们需要此时ClassA的实例被找到
  • SingletonObjects集合中不存放半成品,所以需要一个新的Map集合来存储半成品(第二级缓存????)
  • 为了保证提前暴露的对象,和最终的对象是一个,我们需要在获取这个提前暴露的对象的时候,也需要判断是否进行提前代理
  • 那么这个时候,我们就需要一个第三级缓存。(beanName,获取一个ObjectFactory的工厂类,该类就可以添加产生代理对象的逻辑)
  • 此时去三级缓存中,可以找到该classA对应的ObjectFactory,通过该ObjectFactory获取代理对象获取原对象
  • 换句话说,此处已经找到bean了(只是这个bean还没有完成完成创建,但是bean对象的引用已经获取到了)
  • 此时因为ClassA实例已经被new出来(提早暴露的循环引用)
  • 2.3 ClassB初始化
  • 2.4 ClassB实例放入SingletonObjects集合中

3.ClassA初始化
4.ClassA实例放入SingletonObjects集合中(才是对外暴露的引用集合)

三、Bean的初始化

非循环依赖场景下,如果要产生代理对象,在这个子流程中产生

三级缓存机制

spring中解决setter方法产生的循环依赖问题是通过三级缓存来解决的?

  • singletonObjects:第一级缓存
  • 存储的是最终的单例Bean(暴露给spring容器之外的客户端调用使用的)
  • key是beanName
    value是成品的Bean实例对象
  • earlySingletonObjects:第二级缓存
  • 存储的是正在创建中的单例Bean(暴露给spring框架内调用使用的)
  • key是beanName
    value是半成品的Bean实例对象
  • singletonFactories:第三级缓存
  • 存储的是ObjectFactory(工厂对象),该ObjectFactory是提前保存了正在创建中的Bean实例(new之后,set之前)
  • key是beanName
    value是提早产生Bean实例的对象工厂ObjectFactory

ObjectFactory的作用:

  • 保存提早暴露的单例Bean的引用
  • 针对保存的Bean实例,通过BeanPostProcessor进行判断,看一看该Bean是否需要被功能增强,如果需要,则在此时针对该Bean产生代理对象
  • 将产生的代理对象,放入二级缓存,同时删除该beanName对应的三级缓存数据

三级缓存的存放时间

  • 第三级缓存:Bean实例化之后,属性填充之前,经过判断,将Bean实例封装到ObjectFactory,然后将ObjectFactory放入三级缓存
  • 第二级缓存:当第一次从三级缓存中的ObjectFactory中获取被他保存的Bean实例,获取之后,就会放入二级缓存中
  • 第一级缓存:当第一次完整的创建完Bean实例之后(三部曲之后),才会放入一级缓存,同时清除二级和三级缓存。

三级缓存获取对象的顺序

  • 先从一级缓存中查找
  • 再从二级缓存中查找
  • 最后没有则从三级缓存中获取
  • ObjectFactory获取对象,有可能获取的是代理对象
  • 将获取到的对象,【放入二级缓存】,同时删除此beanName对应的三级缓存数据

三级缓存添加对象的顺序

  • 先将ClassA第一步new出来的对象,【添加到三级缓存】中的ObjectFactory里面保存。

思考:

  • 如果一个目标对象被aop动态加上事务增强功能(代理对象)的话,那么spring容器中存储的是目标对象,还是增强之后的对象,还是都存储?
    答案:spring只会存储一个对象,如果目标对象被AOP产生了代理对象,那么存储的就是代理对象。
  • aop针对目标对象产生代理对象,是发生在bean创建的哪个流程呢?
    答案:是发生在Bean初始化的过程中,具体说,是发生在Bean调用初始化方法之后,去进行AOP流程

Spring Aop核心概念

AOP:面向切面编程
OOP:面向对象编程

  • 结论:AOP可以更好的去补充OOP的缺点

AOP作用:可以针对目标对象进行无感知(不修改目标对象的代码的情况下)的【功能增强】。

功能一般分为业务功能和系统功能。

  • 业务功能:业务操作
  • 系统功能:事务、日志、安全等

AOP可以将业务功能和系统功能进行拆分,专人干专事。

AOP是通过哪种方式去实现的无感知功能增强的呢?

  • 静态织入(了解)
    使用字节码拼接技术,在编译期间,针对目标类对应的class文件进行静态编码(asm)
  • 动态织入(掌握)
    使用动态代理技术,在运行期间,针对目标对象进行动态代理

AOP是一种思路,它的实现产品有多种:AspectJ、Spring AOP、Spring整合AspectJ

AOP核心概念分析:

  • 连接点(Joinpoint):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • 切入点(Pointcut):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  • 目标对象(Target):代理的目标对象
  • 织入(Weaving):是指把增强类应用到目标对象来创建新的代理对象的过程
  • 代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类
  • 通知/增强(Advice):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • 切面/通知器(Aspect/Advisor):是切入点和通知的结合。
  • 引介(Introduction):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Filed

代理技术

  • 静态代理
    在编译期间,为目标类,编写一个代理类,缺点是会编写很多的代理类
  • 动态代理
    在运行期间,为目标类,利用动态代理技术产生代理对象