A. spring为什么要使用三级缓存解决循环依赖
首先清楚spring中bean 的加载过程:
1 解析需要spring管理的类为beanDefinition
2 通过反射实例化对象
3 反射设置属性
4初始化,调用initMethod等。(postConstruct也是在这执行)
循环依赖的问题: a依赖b,b依赖a。
在a实例化之后会先将a放入到缓存中,然后给a设置属性,去缓存中查到b。此时找不到就开始b的创建。b实例化之后,放入到缓存中,需要给a设置属性,此时去缓存中查到a设置成功。然后初始化。成功后将b放入一级缓存。这个时候a在给自己属性b设置值的时候就找到了b,然后设置b。完成属性设置,再初始化,初始化后a放入一级缓存。
解决代理对象(如aop)循环依赖的问题。
例: a依赖b,b依赖a,同时a,b都被aop增强。
首先明确aop的实现是通过 postBeanProcess后置处理器,在初始化之后做代理操作的。
为什么使用三级缓存原因:
1 只使用二级缓存,且二级缓存缓存的是一个不完整的bean
如果只使用二级缓存,且二级缓存缓存的是一个不完整的bean,这个时候a在设置属性的过程中去获取b(这个时候a还没有被aop的后置处理器增强),创建b的过程中,b依赖a,b去缓存中拿a拿到的是没有经过代理的a。就有问题。
2 使用二级缓存,且二级缓存是一个工厂方法的缓存
如果二级缓存是一个工厂的缓存,在从缓存中获取的时候获取到经过aop增强的对象。可以看到从工厂缓存中获取的逻辑。
protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && ()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bpinstanceof ) {
ibp = () bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
a依赖b,b依赖a,c。c又依赖a。a,b,c均aop增强。
加载开始: a实例化,放入工厂缓存,设置b,b实例化,设置属性,拿到a,此时从工厂缓存中拿到代理后的a。由于a没加载完毕,不会放入一级缓存。这个时候b开始设置c,c实例化,设置属性a,又去工厂缓存中拿对象a。这个时候拿到的a和b从工厂缓存不是一个对象。出现问题。
3 使用二级缓存,二级缓存缓存的是增强后的bean。这个与spring加载流程不符合。spring加载流程是:实例化,设置属性,初始化,增强。在有循环引用的时候,之前的bean并不会增强后放入到二级缓存。
综上1,2,3 可知二级缓存解决不了有aop的循环依赖。spring采用了三级缓存。
一级缓存 singletonObjects 缓存加载完成的bean。
二级缓存 earlySingletonObjects 缓存从三级缓存中获取到的bean,此时里面的bean没有加载完毕。
三级缓存 singletonFactories 。缓存一个objectFactory工厂。
场景:a依赖b,b依赖a和c,c依赖a。并且a,b,c都aop增强。
加载过程:
a实例化,放入三级工厂缓存,设置属性b,b实例化放入三级缓存。b设置属性a,从三级工厂缓存中获取代理后的对象a,同时,代理后的a放入二级缓存,然后设置属性c,c实例化放入三级缓存,设置属性a,此时从二级缓存中获取到的代理后的a跟b中的a是一个对象,属性a设置成功。c初始化,然后执行后置处理器。进行aop的增强。增强后将代理的c放入到一级缓存,同时删除三级缓存中的c。c加载完成,b得到c,b设置c成功。b初始化,然后执行后置处理器,进行aop增强,将增强后的代理对象b放入到一级缓存。删除三级缓存中的b。此时 a拿到b,设置属性b成功,开始初始化,初始化后执行后置处理器。在aop的后置处理器中有一个以beanName为key,经过aop增强的代理对象为value的map earlyProxyReferences。
这个时候 后置处理器处理对象a的时候,
public (@Nullable Object bean, String beanName) {
if (bean !=null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
也就是 发现这个beanName已经被代理后就不在代理。这个时候执行后置处理器后,a还是未经代理的对象a。此时a再通过getSingleton 重新从缓存中获取一下a。
Object earlySingletonReference = getSingleton(beanName, false);
false 表示不从三级缓存中取,只从一级,二级缓存中获取。
这个时候能拿到二级缓存中的a。二级缓存中的a也是经过代理后的a。
然后将代理后的a放入到一级缓存中。a加载完毕。
放入一级缓存的过程 :
addSingleton(beanName, singletonObject);
从三级工厂缓存中获取对象:
protected ObjectgetEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && ()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bpinstanceof ) {
ibp = () bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
其中 AbstractAutoProxyCreator实现该接口。
public ObjectgetEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
wrapIfNecessary()就是真正执行代理的。
bean初始化之后执行的后置处理器:
其中AbstractAutoProxyCreator 实现了该接口。
B. Spring AOP 一般用在什么场景中
AOP,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。
(2)aop在缓存设计上的应用扩展阅读:
AOP/OOP区分
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
C. “Spring ”“AOP 容器”不看源码就带你认识核心流程以及运作原理
前一篇文章主要介绍了 spring 核心特性机制的 IOC 容器机制和核心运作原理,接下来我们去介绍另外一个较为核心的功能,那就是 AOP 容器机制,主要负责承接前一篇代理模式机制中动态代理:JDKProxy 和 CglibProxy 的功能机制之后,我们开始研究一下如何实现一下相关的 AOP 容器代理机制的。
实现的基本实现原理就是后置处理器:BeanPostProcessor 机制,实现动态化植入机制。
bean 在初始化的时候会进行调用对应的 BeanPostProcessor 的对应的方法会进行织入。
主要取决于 wrapIfNecessary 方法:
如果是基础设施类型,则直接回进行返回该 bean 对象,不会进行相关的初始化对应的 aspectj 的动态织入机制。
会进行寻找相关的 Bean 对应的何时的加强通知类。
则会对该 bean 对象,额外进行增强操作生成相关的代理对象,并返回该执行之后的对象,否则会直接返回该对象即可。
getAdvicesAndAdvisorsForBean 方法是我们筛选 Advice 增强类的核心方法,主要用于过滤和筛选对应该 bean 的何时的增强器数组信息。
主要用于调用 的**findCandidateAdvisors()**方法,其内部会进行先关的核心构建相关的 Aspectj 的类的相关实现操作
advisorsFactory.getAdvisors 获取通知器
切点类处理操作到此为止,还不完整接下来才是构建动态代理对象的真正执行操作,
扩展相关的筛选出的通知器列表,extendAdvisors 方法,通知器列表首部添加一个 DefaultPointcutAposr 类型的通知器,也就是 ExposeInvocationInterceptor.ADVISOR 的实现机制。
proxy-target-class 的属性值,代表是否可以支持代理实现类,默认采用的 false 代表着,当 bean 有实现接口的时候,会直接采用 jdk 的动态代理机制生成代理对象,如果是 true,则代表着使用 cglib 进行生成代理对象。
复制代码
前提是必须要配置相关的 expose-proxy 属性配置值为 true,才会进行暴露对应的代理机制。
为了解决目标方法调用同对象中的其他方法,其他方法的切面逻辑是无法实现,因为会涉及到相关的 this 操作而不是 proxy 对象机制。
可以实现使用 AopContext.currentProxy()强制转换为当前的代理对象。
获取相关的对应方法的拦截器栈链路,如果没有获取到相关的缓存链路,则会直接调用相关的 获取先关的拦截器链。
会进行先关的 PointcutAdvisor 类型通知器,这里会调用相关的通知器所持有的切点(Pointcut)对类和方法进行匹配,匹配冲过这说明相关的向当前的方法进行织入逻辑控制。此外还会通过 geIntercptors()方法对非 MethodIntercptor 类型的通知进行转换。返回相关的拦截器数组,并且随后存入缓存中。
则会直接通过代理机制的反射控制进行调用执行即可。
则例如 jdkDynamicAutoProxy 对象进行调用构建 ReflectiveMethodInvocation 对象,例如它的 process 方法启动拦截器栈的 invoke 方法。
处理返回值,并且返回该值。