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
代理技术
- 静态代理
在编译期间,为目标类,编写一个代理类,缺点是会编写很多的代理类 - 动态代理
在运行期间,为目标类,利用动态代理技术产生代理对象