补充:创建代理
要点
- 要完全理解循环依赖,需要理解代理对象的创建时机
- 掌握ProxyFactory创建代理的过程,理解Advisor, Advice, Pointcut 与Aspect
- 掌握AnnotationAwareAspectJAutoProxyCreator筛选Advisor合格者,创建代理的过程
1、设置目标对象
2、设置环绕通知(调用目标)
3、创建代理对象,调用方法
现在是调用每个方法都会进行功能的增强。但是想要一种调用foo()方法的时候功能增强,调用bar()方法的时候不增强,怎么办?
这个时候需要引入切点。
aspect=通知(advice)+切点(pointcut),一个切面类中可能有一到多个通知方法。
advisor=更细粒度的切面,包含一个通知和切点
addAdvice只能加一个通知,不能考虑到切点。使用advisor将切点和通知结合起来。
发现只有foo()方法被增强了,bar()方法没有被增强。
代理的模式:生成了子类进行代理(CGLIB代理)
JDK动态代理:利用拦截器加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理。
代理对象调用方法的时候内部要先找到切面,通过切面找到切点是不是跟方法匹配,如果跟方法匹配了再去调用方法的通知。
切面Advisor的信息存到哪里去了呢?
会在代理对象里记录关联这些advisors切面信息。
代理对象当中会间接的引用到advisor切面
将advisor存在advisors集合里
每个方法都会对应一个advisor切面
wrapIfNecessary():判断是否满足条件创建代理对象。满足则创建,不满足就不创建。例如切面类就不需要创建代理对象,还有那些不满足切面表达式的目标也不需要创建代理对象。
总结
-
最基本的切面 是Advisor, - -个Aspect切面对应- -到多个Advisor
-
最基本的Advice是MethodInterceptor(环绕通知), 其它Advice最终都将适配为MethodInterceptor
-
创建代理的方式
- 实现了用户自定义接口,采用jdk动态代理.
- 没有实现用户自定义接口,采用cglib代理
- 设置了setProxyTargetClass(true),统-采用cglib代理
-
切面、切点、通知等不会被代理
-
AnnotationAwareAspectAutoProxyCreator(自动代理后置处理器)调用时机:创建阶段、依赖注入阶段(如果在这发生了循环依赖,也会调用它来创建代理对象)、初始化阶段(调用它来创建代理对象)
一级缓存的作用
保证单例bean只会创建一次
但是它不能解决循环依赖问题
三级缓存的作用
这里老师声称的二级缓存其实是spring中的三级缓存。
singletonObjects :一级缓存
singletonFactories:三级缓存
earlySingletonObjects:二级缓存
A对象实例化好之后将其放入到singletonFactories里(A对象后续的依赖注入和初始化都还没有进行)
但是spring中的三级缓存不能解决循环依赖中有代理的情况。
一级缓存里放的是成品对象(即实例化–依赖注入–初始化好之后的对象,有的对象在初始化之前可能还进行了功能增强。)
spring的三级缓存里面放的是半成品的对象,仅仅只是实例化好的对象。
问题:在B进行A的依赖注入的时候,会去三级缓存中取出半成品的A,但是我们需要给B注入的应该是功能增强后的进行了AOP织入的A。
二级缓存的作用
上面存在的问题,代理对象创建过晚
在A实例化好之后,将工厂对象放入到singletonFactories里面,再去进行依赖注入.B也一样,B在进行A的依赖注入的时候去singltonFactories里面去去A的工厂对象,这个时候工厂对象进行判断,看是不是发生了循环依赖, 如果发生了循环依赖, A工厂就会去创建A的代理对象.创建好之后给B的属性注入上,并且将该代理对象放入到earlySingletonObjects二级缓存里面. B创建好之后,将实例对象返回给A, A注入上之后要进行相关的判断,判断A的代理对象是否已经被创建. (就是去二级缓存earlySingletonObjects里面有没有创建一个代理对象A), 最后将成品对象A放入到一级缓存里面, 清空二级和三级缓存.
重点:创建一个工厂对象放入到三级缓存,让工厂对象判断有没有循环依赖问题,有就提前创建代理对象,没有就不创建. 如果工厂已经把代理对象创建好了在二级缓存里面能够取到,那就不用再创建了.
二级缓存就是为了防止在循环依赖的过程中出现代理的情况。
循环依赖—构造循环依赖
前面讲的spring的set方法的循环依赖问题,是可以通过spring的三级缓存解决的。
三级缓存的过程:
在A实例化之后将A封装成一个工厂对象放入到三级缓存中,在B进行注入的时候可以在三级缓存中拿到A的实例进行属性注入(当然要判断一下是否有代理的情况,如果A需要代理对象,那A的工厂对象会创建一个代理对象,将其放入到二级缓存中。不需要就直接将原型放入到二级缓存中)。完成了B的创建之后,将其放入到一级缓存中,A将创建好的B进行属性注入。
但是在构造循环依赖中A的实力都创建不了,因为他是有参构造,参数都没有提供,A的实例自然也创建不了。
构造循环依赖解决思路
方法一
在构造方法的参数上加一个@Lazy注解 ,给一个代理对象,通过代理可以间接地找到我们的目标真实对象。
方法二
给a一个对象工厂
方法三
这个需要引入相关的依赖,它的作用和上面的工厂对象是一样的。
@Lazy是加在构造方法的参数上,这样保证以后注入参数的时候,注入的是一个代理对象,而不是真正的目标对象。通过代理对象延迟目标对象的获取,从而解决构造函数依赖。
使用@Scope注解,并且指定proxyMode=ScopedProxyMode.TARGET_CLASS,这样在创建B的时候会创建代理对象。
需要注意这个注解要用配置类的方式或者组件扫描的方式才能扫描到这个注解,因此在这个类的上面还要加上@Component注解。
工厂解决方式优于代理对象(不用生成代理对象),代理对象中@Lazy > @Scope
总结
单例set方法(包括成员变量)循环依赖,Spring 会利用三级缓存解决,无需额外配置
-
一级缓存存放成品对象
-
二级缓存存放发生了循环依赖时的产品对象(可能是原始bean,也可能是代理bean )
-
三级缓存存放工厂对象,发生循环依赖时,会调用工厂获取产品
-
Spring期望在初始化时创建代理,但如果发生了循环依赖,会由工厂提前创建代理,后续初始化时就不必重复创建代理
-
二 级缓存的意义在于, 如果提前创建了代理对象, 在最后的阶段需要从二级缓存中获取此代理对象, 作为最终结果
构造方法及多例循环依赖解决办法
- @Lazy
- @Scope
- ObjectFactory & ObjectProvider
- Provider
代理,但如果发生了循环依赖,会由工厂提前创建代理,后续初始化时就不必重复创建代理
- 二 级缓存的意义在于, 如果提前创建了代理对象, 在最后的阶段需要从二级缓存中获取此代理对象, 作为最终结果
构造方法及多例循环依赖解决办法
- @Lazy
- @Scope
- ObjectFactory & ObjectProvider
- Provider