1、spring 循环依赖与 spring AOP 底层原理分析
-
什么是循环依赖?
-
什么是单例池,什么是一级缓存?
单例池其实是一个个 Map<bean名字,bean 对象> 对象 -
什么是二级缓存,它的作用是什么?
-
什么是三级缓存,它的作用是什么?
-
为什么Spring一定要用三级缓存来解决循环依赖?
-
三级缓存解决循环依赖的底层源码分析
-
Spring AOP的底层原理分析
-
有哪些情况下的循环依赖是Spring解决不了的?
-
为什么@Lazy注解可以用来解决循环依赖
1、什么是循环依赖?
很简单,就是A对象依赖了B对象,B对象依赖了A对象。
// A依赖了B
class A{
public B b;
}
// B依赖了A
class B{
public A a;
}
那么循环依赖是个问题吗?
如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。
A a = new A();
B b = new B();
a.b = b;
b.a = a;
这样,A,B就依赖上了。
但是,在Spring中循环依赖就是一个问题了,为什么?
因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,下文详细来说。
要明白Spring中的循环依赖,得先明白Spring中Bean的生命周期
Bean的生命周期
这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程。
Bean的生命周期指的就是:在Spring中,Bean是如何生成的?
、Bean的生命周期指的就是:在Spring中,Bean是如何生成的?
被Spring管理的对象叫做Bean。Bean的生成步骤如下:
- Spring扫描class得到BeanDefinition
- 根据得到的BeanDefinition去生成bean
- 首先根据class推断构造方法
- 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
- 填充原始对象中的属性(依赖注入)
- 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
- 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可
可以看到,对于Spring中的Bean的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多很多,比如Aware回调、初始化等等,这里不详细讨论。
可以发现,在Spring中,构造一个Bean,包括了new这个步骤(第4步构造方法反射)。
得到一个原始对象后,Spring需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?
比如上文说的A类,A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。如果此时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;如果此时BeanFactory中不存在B对应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。
问题就出现在第二种情况,如果此时B类在BeanFactory中还没有生成对应的Bean,那么就需要去生成,就会经过B的Bean的生命周期。
那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这里就出现了循环依赖:
ABean创建–>依赖了B属性–>触发BBean创建—>B依赖了A属性—>需要ABean(但ABean还在创建过程中)
从而导致ABean创建不出来,BBean也创建不出来。
这是循环依赖的场景,但是上文说了,在Spring中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是三级缓存。
例子
创建一个AService
@Component("aService")
public class AService {
@Autowired
private BService bService;
public void test (){
System.out.println(bService);
}
}
BService
@Component("bService")
public class BService {
@Autowired
private AService aService;
public void test(){
System.out.println(aService);
}
}
AService的 大概生命周期
1、实例化 (new AService)
2、填充属性 bservice
2.1、实例化 (new BService)
2.2、填充属性 aservice 这里就会找不到 aservice 的bean
2.3、填充其他属性
2.4、做其他事情(AOP)
2.5、生成 Bean
3、填充其他属性
4、做其他事情(AOP)
5、生成 Bean 如果是单例 Bean 需要放入单例池
如果是原型Bean ,就不需要做其他事情
这里就会产生循环依赖
如何打破这个循环 ?
三级缓存
一级缓存为:singletonObjects 单例池
二级缓存为:earlySingletonObjects
三级缓存为:singletonFactories
先稍微解释一下这三个缓存的作用,后面详细分析:
• singletonObjects中缓存的是已经经历了完整生命周期的bean对象。
• earlySingletonObjects比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
• singletonFactories中缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的。
这里引入的map 相当于 二级缓存
1、实例化 (new AService) 用一个map 放入 aService的原始对象
2、填充属性 bservice 去单例池寻找
2.1、实例化 (new BService) 用一个map 放入 bService的原始对象
2.2、填充属性 aservice 这里就会找到 aservice原始对象
2.3、填充其他属性
2.4、做其他事情(AOP)
2.5、生成 Bean
3、填充其他属性
4、做其他事情(AOP)
5、生成 Bean 如果是单例 Bean 需要放入单例池
这里就会打破循环 ,用一个map 来打破.
解决循环依赖思路分析
先来分析为什么缓存能解决循环依赖。
上文分析得到,之所以产生循环依赖的问题,主要是:
A创建时—>需要B---->B去创建—>需要A,从而产生了循环
那么如何打破这个循环,加个中间人(缓存)
A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时A的Bean依赖了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。
因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始对象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。
从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories呢?
这是难点,基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。
AOP :切面
0、 createSet (‘aService’) 放入 正在创建中的bean
1、实例化 (new AService) 用一个map 放入 aService的原始对象
2、填充属性 bservice 去单例池寻找
bService 的生命周期
2.1、实例化 (new BService) 用一个map 放入 bService的原始对象
2.2、填充属性 aservice 这里就会找到 bservice原始对象 这里需要放入代理对象()
2.3、填充其他属性
2.4、做其他事情(AOP) 需要AOP 这里产生 bService的代理对象
2.5、生成 Bean
3、填充其他属性
4、做其他事情(AOP) 需要AOP 这里产生 aService的代理对象或者原始对象
5、生成 Bean 如果是单例 Bean 的代理对象 需要放入单例池
6、 acreateSet.remove() // 移除 正在创建中的bean
代理对象和原始对象有一定关系.
B依赖的A和最终的A不是同一个对象。
**如何接口对象附值问题?
提前进行AOP 将 代理对象放入 二级缓存中去,**在2.2 的时候.
1、先判断是否需要AOP ?
2.2步判断的时候,提前 看是否有循环依赖. 如果有循环依赖,就会提前进行AOP .
在2.2 步的时候, 发现 aSevice 正在创建中,这时就表示出现了循环依赖. 通过正在创建中的集合,createSet 集合放入正在创建中的bean
2、如何生成代理对象?
进行AOP
例子
创建一个AService
@Component("aService")
public class AService {
@Autowired
private BService bService;
@Autowired
private CService cService;
public void test (){
System.out.println(bService);
}
}
BService
@Component("bService")
public class BService {
@Autowired
private AService aService;
public void test(){
System.out.println(aService);
}
}
BService
@Component("cService")
public class CService {
@Autowired
private AService aService;
public void test(){
System.out.println(aService);
}
}
AOP :切面
0、 createSet (‘aService’) 放入 正在创建中的bean
1、实例化 (new AService) 用一个map 放入 aService的原始对象
2、填充属性 bservice 去单例池寻找
bService 的生命周期
2.1、实例化 (new BService) 用一个map 放入 bService的原始对象
2.2、填充属性 aservice 这里就会找到 bservice原始对象 这里需要放入代理对象 这里放入二级缓存
2.3、填充其他属性
2.4、做其他事情(AOP) 需要AOP 这里产生 bService的代理对象
2.5、生成 Bean
cService 的生命周期
2.1、实例化 (new CService) 用一个map 放入 aService的原始对象
2.2、填充属性 aservice 这里就会找到 aservice原始对象 这里需要放入代理对象 这里去二级缓存中 寻找 aService 的代理对象
2.3、填充其他属性
2.4、做其他事情(AOP) 需要AOP 这里产生 bService的代理对象
2.5、生成 Bean
3、填充其他属性
4、做其他事情(AOP) 需要AOP 这里产生 aService的代理对象
4.5、 从二级缓存 取 代理对象
5、生成 Bean 如果是单例 Bean 的代理对象 需要放入单例池
6、 acreateSet.remove() // 移除 正在创建中的bean
//
这里的aService 代理对象被附值了两次 bservice 、 cService
这里真正用到了二级缓存:
一级缓存 singletonObjects Map<beanName ,对象> 单例池
保证 bean 不重复.
二级缓存, earlySingletonObjects Map<beanName ,对象> 对与同一个 Bean Name ,有多个 bean , 是一个不完整的bean
对象,都是单例的 , 有可能是原始对象,有可能是代理对象,解决代理对象不重复.
三级缓存
map< beanname ,singletonFactories>
singletonFactories: lambad 表达式 主要放入原始对象,用于AOP 把原始对象转 代理对象.
例子:
切面
@Aspect
@Component
public class SpringAsPect {
@Before("execution(public void com.spring.service.AService.test())")
public void SpringBefore(){
System.out.println("AOP 功能!");
}
}
AOP:
代理对象与原始对象有什么联系?
代理对象是没有值的,原始对象有值,代理对象是用到了原始对象.
如何使用jdk 的动态代理, 实现一个接口
如何使用Spring的代理对象 ,用SpringProxyFactory
例子:
public class SpringProxyFactory {
public static void main(String[] args) {
AService aService = new AService();// 原始对象
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(aService); // 原始对象
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
// method 表示执行的方法 objects // 表示执行方法的参数, o 表示原始对象
method.invoke(o,objects); // 执行方法
System.out.println("代理逻辑之前执行的方法!");
}
});
// 得到代理对象
AService aService1 = (AService) proxyFactory.getProxy();
aService1.test();
}
}
0、 createSet (‘aService’) 放入 正在创建中的bean
1、实例化 (new AService) 用一个map 放入 aService的原始对象 singletonFactories<beanname, lambda()> ->
lambda() -> getEarlyBeanRefernce(beanName,mbd,beand)
2、填充属性 bservice 去单例池寻找
bService 的生命周期
0、 createSet ('bService') 放入 正在创建中的bean
2.1、实例化 (new BService) 用一个map 放入 bService的原始对象
2.2、填充属性 aservice 从单例池找->二级缓存-> 找不到 ->
createSet->出现了循环依赖-> 从三级缓存里寻找 lambad() singletonFactories->原始对象->aop-> 代理对象或者原始对象.-> 放入二级缓存.
2.3、填充其他属性
2.4、做其他事情(AOP) 判断是否已经AOP, 要AOP 这里产生 bService的代理对象,
2.5、生成 Bean
3、填充其他属性
4、做其他事情(AOP) 判断是否已经进行AOP,需要AOP 这里产生 aService的代理对象
4.5、 从二级缓存 取 代理对象
5、生成 Bean 如果是单例 Bean 的代理对象 需要放入单例池
6、 acreateSet.remove() // 移除 正在创建中的bean
//
解决循环依赖:
**createSet0** 正在创建的bean
一级缓存: singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean
**二级缓存:**earlySingletonObjects:缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的BeanPostProcessor等生命周期
**三级缓存** : singletonFactories:缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接得到一个原始对象)。
**earlyProxyReferences**: 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。
构造方法的缓存依赖:
@Component("aService")
public class AService {
@Autowired
private BService bService;
public AService (BService bService){
this.bService=bService;
}
public void test (){
System.out.println(bService);
}
}
@Component(“bService”)
public class BService {
@Autowired
private AService aService;
public BService (AService aService){
this.aService=aService;
}
public void test(){
System.out.println(aService);
}}
用 @Lazy 注解去解决.
那么如何解决这个问题?这个问题可以说没有办法解决。
因为在一个Bean的生命周期最后,Spring提供了BeanPostProcessor可以去对Bean进行加工,这个加工不仅仅只是能修改Bean的属性值,也可以替换掉当前Bean。