前序
之前有很多小兄弟问我,在面试过程中经常被问道Spring是如何解决循环依赖的?以及为什么是多级缓存来处理循环依赖?等等问题,种类蛮多的,自己头都有点大;最近几天把相关资源整理一下,这里把过程描述一下吧;
一、开场——模拟循环依赖是怎么出现的
我们先来一个例子(先来一斤老白干漱漱口),模拟一下循环依赖到底是怎么产生的;
1.1)先来创建2个测试使用的Bean,以及一个接口
/**
* 接口描述: 循环依赖测试接口
* <br />
* 先来一个接口;PS:自己已经习惯了,做事情之前,先抽象模拟流程流向,再模拟数据流向,最后才将逻辑补全;
* <p/>
* 这里阐述一下我个人的看法;之前有个哥们不知道从哪里听说的,每一个处理都要有一个自己独立的接口,一个接口服务一个类
* 如果你再开发中用一个接口描述多个类,他会把你当怪物一样,并且告诉你:“这是Spring的标准”;一个接口对应多个类的处理以后会吃大亏;
* 我找遍了Spring的相关资料,真的没有找到这个标准,不知道是不是本人太菜没有找对地方;还是以后吃大亏的时候再说吧
* @author XXSD
* @version 1.0.0
* @date 2021/3/25 0025 下午 8:21
*/
public interface ICirulationDependencyTest {
/**
* 功能描述:测试方法
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:22
*/
void demoTest();
}
测试类——A:
/**
* 类描述: 循环依赖测试Bean——A
*
* @author XXSD
* @version 1.0.0
* @date 2021/3/24 0024 下午 8:42
*/
public class CirulationDependencyTestBeanA implements ICirulationDependencyTest {
@Autowired
private CirulationDependencyTestBeanB cirulationDependencyTestBeanB;
/**
* 功能描述:循环依赖测试Bean
* @author : XXSD
* @date : 2021/3/25 0025 下午 9:06
*/
public CirulationDependencyTestBeanA() {
System.out.println("CirulationDependencyTestBeanA——实例化");
}
@Override
public void demoTest(){
System.out.println("调用了CirulationDependencyTestBeanA的demoTest方法");
}
}
测试类——B:
/**
* 类描述: 循环依赖测试Bean——B
*
* @author XXSD
* @version 1.0.0
* @date 2021/3/24 0024 下午 8:42
*/
public class CirulationDependencyTestBeanB implements ICirulationDependencyTest {
@Autowired
private CirulationDependencyTestBeanA cirulationDependencyTestBeanA;
/**
* 功能描述:循环依赖测试Bean
* @author : XXSD
* @date : 2021/3/25 0025 下午 9:07
*/
public CirulationDependencyTestBeanB() {
System.out.println("CirulationDependencyTestBeanB——实例化");
}
@Override
public void demoTest(){
System.out.println("调用了CirulationDependencyTestBeanB的demoTest方法");
}
}
1.2)正式模拟开始
/**
* 类描述: 循环依赖——模拟
*
* @author XXSD
* @version 1.0.0
* @date 2021/3/24 0024 下午 8:39
*/
public class CircularDependency {
/**
* 属性描述:Bean定义(Beandefinition)容器
* <br />
* 这里模拟一个和Spring一模一样的容器,用于存储BeanDefinition对象
*
* @date : 2021/3/24 0024 下午 8:40
*/
private static final Map<String, BeanDefinition> BEAN_DEFINITION_MAP = new ConcurrentHashMap<>(16);
/**
* 属性描述:模拟一级缓存容器
* <br />
* 在Spring中一级缓存容器的变量名称为:singletonObjects
* @date : 2021/3/25 0025 下午 9:03
*/
private static final Map<String, Object> LEVEL_ONE_CACHE_MAP = new ConcurrentHashMap<>(16);
/**
* 功能描述:初始化
* <br />
* 将测试用的Bean放入虚拟容器中
*
* @author : XXSD
* @date : 2021/3/24 0024 下午 8:45
*/
public CircularDependency() {
/*
* 这里先初始化2个Bean定义
* */
RootBeanDefinition beanA = new RootBeanDefinition(CirulationDependencyTestBeanA.class);
RootBeanDefinition beanB = new RootBeanDefinition(CirulationDependencyTestBeanB.class);
/*
* 这里模拟将Bean定义装入缓存中
* 注意:这里在Spring中是通过扫描的方式进行加载的
* */
BEAN_DEFINITION_MAP.put("cirulationDependencyTestBeanA", beanA);
BEAN_DEFINITION_MAP.put("cirulationDependencyTestBeanB", beanB);
}
/**
* 功能描述:这里模拟了Spring的获取Bean的方法
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:13
*/
public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
/*
* 回顾之前所讲到的一个Bean的生命周期:实例化、属性赋值、初始化、添加到一级缓存中
* 第一步:模拟实例化;获取到Class,通过反射进行实例化;
* 在Spring中在对Bean的实例化过程中还会调用工厂方法、可选有参的构造方法,这里仅仅是模拟,不要在意这些细节;
* */
final Class<?> beanClass = beanDefinition.getBeanClass();
final Object instance = beanClass.newInstance();
/*
* 第二步:开始模拟属性赋值;先获取所有的属性,通过反射进行赋值;
* 首先判断属性上是否存在@Autowired
* */
final Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
final Autowired annotation = declaredField.getAnnotation(Autowired.class);
if(annotation!=null){
/*
* 说明属性是要求注入的,这里开始属性赋值的处理;
* 先开启访问权限;递归调用本方法获取到需要注入的对象;
* 注意:在Spring中不仅可以根据名称获取,还可以根据类型以及构造函数;
* 即:byName、byType、constructor
* 这里还是不要在意这些细节,模拟而已;
* */
declaredField.setAccessible(true);
declaredField.set(instance, getBean(declaredField.getName()));
}
}
/*
* 第三步:有没有实现指定的方法或规则,比如init-mthod等;这里跳过吧,不需要去处理了;
* 第四步:放入一级缓存中
* */
LEVEL_ONE_CACHE_MAP.put(beanName, instance);
return instance;
}
/**
* 功能描述:执行入口
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:12
*/
public static void main(String[] values) throws InstantiationException, IllegalAccessException {
final CircularDependency circularDependency = new CircularDependency();
/*
* 这里开始模拟从缓存中获取对应的Bean定义(Beandefinition)对象
* */
final Set<String> beanDefinitonMapKeys = BEAN_DEFINITION_MAP.keySet();
for (String beanDefinitonMapKey : beanDefinitonMapKeys) {
/*
* 上面装入的时候是先放入的是对象A的Bean定义,那么这里肯定是先拿到A再拿到B
* */
circularDependency.getBean(beanDefinitonMapKey);
}
}
}
1.3)执行
上面的代码,我们先执行一下,不出意外的系统反手给你一个“java.lang.StackOverflowError”异常,告诉你:“兄弟死循环了,你想累死我是不???”
那么问题出在什么地方呢?结合异常信息,回顾上面的代码,问题就是出现在:
for (Field declaredField : declaredFields) {
final Autowired annotation = declaredField.getAnnotation(Autowired.class);
if(annotation!=null){
/*
* 说明属性是要求注入的,这里开始属性赋值的处理;
* 先开启访问权限;递归调用本方法获取到需要注入的对象;
* 注意:在Spring中不仅可以根据名称获取,还可以根据类型以及构造函数;
* 即:byName、byType、constructor
* 这里还是不要在意这些细节,模拟而已;
* */
declaredField.setAccessible(true);
//这里递归的时候出现了死循环
declaredField.set(instance, getBean(declaredField.getName()));
}
}
因为在测试类A中有B,在测试类中又存在A,执行到这个递归的时候就会不断的去调用;有人会说“这个写法,在开发中经常使用啊,为啥我没有遇到呢?”;那是因为Spring生态已经帮你解决了这个问题;不要急,我们下面继续;
二、代码改造
2.1)缓存获取
先添加一个从一级缓存中获取的对象的方法
/**
* 功能描述:从缓存中获取对象
* <br />
* 现在一级缓存中获取,如果存在直接返回;
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 9:58
*/
private Object getSingleton(String beanName) {
if (LEVEL_ONE_CACHE_MAP.containsKey(beanName)) {
/*
* 如果存在就直接返回缓存中的对象
* */
return LEVEL_ONE_CACHE_MAP.get(beanName);
}
return null;
}
2.2)getBean方法改造
/**
* 功能描述:这里模拟了Spring的获取Bean的方法
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:13
*/
public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
/*
* 这里先在缓存中找一次,如果有了就直接返回;
* 没有才进行下一步;
* */
final Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
/*
* 回顾之前所讲到的一个Bean的生命周期:实例化、属性赋值、初始化、添加到一级缓存中
* 第一步:模拟实例化;获取到Class,通过反射进行实例化;
* 在Spring中在对Bean的实例化过程中还会调用工厂方法、可选有参的构造方法,这里仅仅是模拟,不要在意这些细节;
* */
final Class<?> beanClass = beanDefinition.getBeanClass();
final Object instance = beanClass.newInstance();
/*
* 直接把最后一步挪到这里,实例化完毕后直接放入一级缓存
* */
LEVEL_ONE_CACHE_MAP.put(beanName, instance);
//。。。。。。。。。。此处的代码就不动了,和原来的一样,看下面的。。。。。。。。。。
/*
* 第三步:有没有实现指定的方法或规则,比如init-mthod等;这里跳过吧,不需要去处理了;
* 第四步:放入一级缓存中
* 这一步移到上面去,这里就不要了
* */
//LEVEL_ONE_CACHE_MAP.put(beanName, instance);
return instance;
}
最后改造一下Main方法
/**
* 功能描述:执行入口
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:12
*/
public static void main(String[] values) throws InstantiationException, IllegalAccessException {
final CircularDependency circularDependency = new CircularDependency();
//。。。。。。。。。。此处的代码可以不动,和原来的一样,加入下面的代码。。。。。。。。。。
/*
* 加入缓存读取后这里直接调用一次试一试
* 注意:这里是直接使用的接口去接返回的对象,世间万物”皆接口“
* */
final ICirulationDependencyTest cirulationDependencyTestBeanA = (ICirulationDependencyTest) circularDependency.getBean("cirulationDependencyTestBeanA");
cirulationDependencyTestBeanA.demoTest();
}
跑起来,现在看看效果:
您老人家是不是惊奇的发现已经不会出现死循环了;到了这里很多童鞋会开始狂暴了:“一级缓存已经可以解决这个循环依赖的问题了,Spring有病吗?非要搞出那么多的事情,做些无用功,装逼到了极致!!!!”
其实不是这样的,Spring是一个生态,生态环境的稳定,必须要考虑很多因素,比如:多线程环境下的Bean创建,还有托扩展性一级维护性等等N多种情况;必须把责任明确,便于日后拓展;
PS:这里啰嗦一下,很多人问我看源码是为了什么?我的答案是:“装逼”最有力的支撑;看这些优秀框架的源码可以学习到很多东西,架构、设计、解决方案、算法、设计模式等等,对你都有很大的提升;人是无法满足的,自己的技能也是一样,永无止境;现在使用的很多所谓的分布式架构解决方案,都是想尽一切办法集成到Spring中,让开发变得更加简单高效;你不看源码,又怎么知道如何将自己多年的经验封装成组件可以集成到Spring中?做太久的CRUD是猿们的基础,但是坐久了会毁人的,不要总是“一杯茶、一包烟、一个Bug改一天”,最后还自我感觉良好,自己的成就感在哪里呢?自己比别人优秀在哪里呢?
回头看看上面的代码逻辑,你会发现,Bean——A实例化后直接放入了一级缓存,但是在处理属性的时候通过递归调用了getBean去创建Bean——B,在创建Bean——B的时候,从一级缓存中虽然可以获取到Bean——A,但是这个BeanA是不完整的,因为A里面的属性还没有初始化完毕;一个残缺的Bean给你有意义吗?你敢直接肆无忌惮的调用吗? 所以接下来,我们继续模拟一下二级缓存的情况;
三、二级缓存
3.1)创建一个二级缓存容器
/**
* 属性描述:模拟二级缓存容器
* <br />
* 在Spring中一级缓存容器的变量名称为:earlySingletonObjects
* 这个二级缓存是用于将完整的Bean与非完整的Bean进行区分,这样做的目的在于职责更加明确,避免读取到不完整的Bean
*
* @date : 2021/3/25 0025 下午 9:03
*/
private static final Map<String, Object> LEVEL_TWO_CACHE_MAP = new ConcurrentHashMap<>(16);
PS:这里有个面试题(但不常见,具体要看面试官的功底和心情):在Spring中如何避免加载到不完整的Bean?其实这个问题就是在问你Spring的缓存作用,在这里就是二级缓存对应的功效,二级缓存专门用于存储非完整的Bean,而一级缓存中存储的是完整的Bean,这也是为什么Spring在获取Bean的时候都是从一级缓存中进行获取的原因,从一级缓存中获取到的Bean都是即拿即用的;
3.2)存储模式的调整
getSingleton方法的变动:
/**
* 功能描述:从缓存中获取对象
* <br />
* 现在一级缓存中获取,如果存在直接返回;
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 9:58
*/
private Object getSingleton(String beanName) {
if (LEVEL_ONE_CACHE_MAP.containsKey(beanName)) {
/*
* 如果存在就直接返回缓存中的对象
* */
return LEVEL_ONE_CACHE_MAP.get(beanName);
}else if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
/*
* 如果一级缓存中不存在,就在二级缓存中获取
* */
return LEVEL_TWO_CACHE_MAP.get(beanName);
}
return null;
}
主方法getBean的变动:这里先把getBean的方法还原成第一步的形态,然后在实例化之后直接加入二级缓存;
/**
* 功能描述:这里模拟了Spring的获取Bean的方法
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:13
*/
public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
/*
* 这里先在缓存中找一次,如果有了就直接返回;
* 没有才进行下一步;
* */
final Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
/*
* 回顾之前所讲到的一个Bean的生命周期:实例化、属性赋值、初始化、添加到一级缓存中
* 第一步:模拟实例化;获取到Class,通过反射进行实例化;
* 在Spring中在对Bean的实例化过程中还会调用工厂方法、可选有参的构造方法,这里仅仅是模拟,不要在意这些细节;
* */
final Class<?> beanClass = beanDefinition.getBeanClass();
final Object instance = beanClass.newInstance();
/*
* 这里将对象放入二级缓存
* */
LEVEL_TWO_CACHE_MAP.put(beanName, instance);
/*
* 第二步:开始模拟属性赋值;先获取所有的属性,通过反射进行赋值;
* 首先判断属性上是否存在@Autowired
* */
final Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
final Autowired annotation = declaredField.getAnnotation(Autowired.class);
if (annotation != null) {
/*
* 说明属性是要求注入的,这里开始属性赋值的处理;
* 先开启访问权限;递归调用本方法获取到需要注入的对象;
* 注意:在Spring中不仅可以根据名称获取,还可以根据类型以及构造函数;
* 即:byName、byType、constructor
* 这里还是不要在意这些细节,模拟而已;
* */
declaredField.setAccessible(true);
declaredField.set(instance, getBean(declaredField.getName()));
}
}
/*
* 第三步:有没有实现指定的方法或规则,比如init-mthod等;这里跳过吧,不需要去处理了;
* 第四步:放入一级缓存中
* */
LEVEL_ONE_CACHE_MAP.put(beanName, instance);
return instance;
}
执行一下,看看结果,是不是依旧执行成功?
二级缓存是不是已经完美解决循环依赖问题了呢?在Spring中的答案是肯定的,解决循环依赖二级缓存足矣;但是Spring为何又加入了一个三级缓存呢?多此一举,难道优势装逼?因为Spring生态规范的原因,三级缓存是非常有必要的,请继续往下看;
四、三级缓存
这里三级缓存,我真的不知道怎么定义它,有人说为了AOP动态代理的处理,也有人说为了优化循环依赖的,总之就是众说纷纭,没有一个具体的定性,您老人家随意找到一个理由来安慰自己吧,不要太纠结了,至于这个3级缓存的作用可能Spring的原开发人员能够解释清楚吧........哈哈哈哈哈!
为什么说为了AOP的动态代理呢? 假设在开发中我们使用了切面进行拦截处理,即使用了@Pointcut("execution(* ...CirulationDependencyTestBeanA.*(..))"),要给CirulationDependencyTestBeanA 创建动态代理,在Spring中创建AOP代理是采用耦合的方式处理的,是在Bean的生命周期中的初始化之后以及实例化之后马上调用当前Bean的后置处理器时完成的,如果没有记错的话Spring貌似使用的是JdkDynamicAopProxy这个类来完成的动态代理;
PS:这里也有个面试题(但不常见,具体要看面试官的功底和心情):请问Spring在什么时候开始创建Bean的AOP代理?这里一定要注意,不要把上面的那段话直接不经过大脑的复述一遍,这里需要装修一下语言哈,否则你会死的很坑爹的(为啥会死的很坑爹,自己抓头发想一想);比较完整的回答是:首先Spring创建动态代理是通过调用Bean的后置处理器来实现的,这样做的好处在于解耦(在这里自己的脑子里一定要想好,试图引导面试官的思维向你的路上引,马上准备一下Spring生态方面的知识,下一步面试官可能会提这方面的问题了);创建时机而言严谨的来讲2种,第一种是在初始化之后调用当前Bean的后置处理器来完成的AOP创建,第二种当出现循环以来的时候会在实例化之后调用当前Bean的后置处理器来完成的AOP创建;在Spring源码中调用的Bean后置处理器是“AbstractAutoProxyCreator”因为这个对象实现了“SmartInstantiationAwareBeanPostProcessor”源码中org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference片段如下:
/**
*这个方法很重要 很多资料都没有说清楚,在解决循环依赖的过程中把三级对象的getObject()的时候,会触发getEarlyBeanReference
* 在该方法中,我们可以通过SmartInstantiationAwareBeanPostProcessor的后置处理器来修改我们早期对象的属性,
* 但是spring内部的SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference没有做任何的处理
* 正符合spring开放式接口规范 留个我们扩展
* @param beanName the name of the bean (for error handling purposes)
* @param mbd the merged bean definition for the bean
* @param bean the raw bean instance
* @return the object to expose as bean reference
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
//判读我们容器中是否有InstantiationAwareBeanPostProcessors类型的后置处理器
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//获取我们所有的后置处理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
/**
* 这里就是调用后置处理器来完成的AOP动态代理
* 判断我们的后置处理器是不是实现了SmartInstantiationAwareBeanPostProcessor接口
*/
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
//进行强制转换
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//挨个调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
在Spring中对Bean的代理存在JDK原生模式与Cglib模式两种,这里如果没有记错貌似默认使用的是JDK动态代理,具体体现在DefaultAopProxyFactory里:
/**
*
* @param config 用来为我们指定我们advisor信息
* 该方法用来创建我们的代理对象
* 所我们的targetClass对象实现了接口,且 ProxyTargetClass 没有指定强制的走cglib代理,那么就是创建jdk代理
* 我们代理的类没有实现接口,那么会直接走cglib代理
* 若我们 ProxyTargetClass 指定为false 且代理类是接口才会走jdk代理 否在我们还是cglib代理
* @return
* @throws AopConfigException
*/
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//判断我们是否前置指定使用cglib代理ProxyTargetClass =true fasle
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//所targetClass是接口 使用的就是jdk代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//cglib代理
return new ObjenesisCglibAopProxy(config);
}
else {
//动态代理
return new JdkDynamicAopProxy(config);
}
}
4.1)模拟代理实现
/**
* 类描述: Spring代理实现模拟
*
* @author XXSD
* @version 1.0.0
* @date 2021/3/26 0026 下午 4:54
*/
public class JdkDynamicAopProxy implements AopProxy {
private Object bean;
public JdkDynamicAopProxy(Object bean) {
this.bean = bean;
}
/**
* 这里仅仅是走个过场,不要在意和纠结
*/
@Override
public Object getProxy() {
return bean;
}
/**
* 这里仅仅是走个过场,不要在意和纠结
*/
@Override
public Object getProxy(ClassLoader classLoader) {
return null;
}
}
4.2)模拟后置处理器
/**
* 类描述: 动态代理后置处理器
*
* @author XXSD
* @version 1.0.0
* @date 2021/3/26 0026 下午 4:07
*/
public class DynamicProxyPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
/**
* 后置处理器回调方法
*
* @param bean the raw bean instance
* @param beanName the name of the bean
* @return the object to expose as bean reference
* (typically with the passed-in bean instance as default)
* @throws BeansException in case of errors
*/
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
/*
* 这里就简单的默认为CirulationDependencyTestBeanA对象需要代理
* 仅仅示例而已,不要在意这些细节
* */
if(bean instanceof CirulationDependencyTestBeanA){
final JdkDynamicAopProxy jdkDyncimcProxy = new JdkDynamicAopProxy(bean);
//这里返回代理的对象
return jdkDyncimcProxy.getProxy();
}
return bean;
}
}
4.3)实例化阶段的动态代理
根据上面所讲,在实例化阶段如果是处于循环依赖的时候就使用AOP进行动态代理;那么如何鉴别当前是否处于循环依赖呢?这里回扣之前的知识“Spring使用了多级缓存的方式来处理,目的在于明确责任,易拓展、易维护、已更新”这里得到了应征,我们将不是一个完整的Bean放入了二级缓存中,那么是不是就可以根据这个条件来进行判断当前是否处于循环以来呢?
PS:在之前一次跟研发童鞋做交流的时候有个脑子转的比较快的童鞋,马上回答在getSingleton方法中如果在二级缓存中获取到对象直接进行代理处理,然后将二级缓存的对象替换为代理对象;这个回答貌似比较完美,但是我随后提问,现在的例子中只有2个Bean相互依赖,那如果是三个Bean呢?B依赖A;C依赖A,A依赖B和C,如果使用这个方法这个对象不就会被代理处理多次吗?您品,您细品;
4.4)示例代码最终形态
根据Spring生态的规则,这里避免耦合,添加一个函数接口:
@FunctionalInterface
public interface FactoryAopFunction<T> {
/**
* 功能描述:获取代理后的Bean对象
* @author : XXSD
* @date : 2021/3/27 0027 下午 12:08
*/
T getAopBean() throws Exception;
}
在示例中添加一个三级缓存容器,以及正在一个正在创建的目录容器;
/**
* 属性描述:模拟三级缓存容器
* <br />
* 在Spring中一级缓存容器的变量名称为:singletonFactories
* 这个三级缓存专门用于管理回调函数接口
*
* @date : 2021/3/25 0025 下午 9:03
*/
private static final Map<String, FactoryAopFunction> LEVEL_Three_CACHE_MAP = new ConcurrentHashMap<>(16);
/**
* 属性描述:模拟创建目录
* <br />
* 在Spring中这个容器名称为:registeredSingletons;用于存储当前正在创建的Bean对象的名称;
* 用于标识当前的对象是否处于创建过程中,是一个类似于出入口的概念
*
* @date : 2021/3/27 0027 下午 1:28
*/
private static final Set<String> CREATION_CATALOG_SET = new HashSet<>();
对getSingleton方法的调整:
/**
* 功能描述:从缓存中获取对象
* <br />
* 现在一级缓存中获取,如果存在直接返回;
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 9:58
*/
private Object getSingleton(String beanName) throws Exception {
//先在一级缓存中获取
final Object bean = LEVEL_ONE_CACHE_MAP.get(beanName);
if (bean == null && CREATION_CATALOG_SET.contains(beanName)) {
/*
* 先看一眼二级缓存中是否存在
* */
if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
return LEVEL_TWO_CACHE_MAP.get(beanName);
}
/*
* 这里就说明是循环依赖,因为以及缓存中不存在,而书签容器中又存在;
* 先通过三件缓存的回调方法中创建一个动态代理,然后放入二级缓存中
* */
final FactoryAopFunction factoryAopFunction = LEVEL_Three_CACHE_MAP.get(beanName);
if (factoryAopFunction != null) {
final Object aopBean = factoryAopFunction.getAopBean();
//放入二级缓存中
LEVEL_TWO_CACHE_MAP.put(beanName, aopBean);
//移除三级缓存的内容,已经处理过了就没有必要在留下了
LEVEL_Three_CACHE_MAP.remove(beanName);
return aopBean;
}
}
return bean;
}
对核心方法的调整:
/**
* 功能描述:这里模拟了Spring的获取Bean的方法
*
* @author : XXSD
* @date : 2021/3/25 0025 下午 8:13
*/
public Object getBean(String beanName) throws Exception {
/*
* 这里先在缓存中找一次,如果有了就直接返回;
* 没有才进行下一步;
* */
final Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
/*
* 需要开始创建Bean
* 创建之前先将Bean的名称放入书签中
* */
if (!CREATION_CATALOG_SET.contains(beanName)) {
CREATION_CATALOG_SET.add(beanName);
}
final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
/*
* 回顾之前所讲到的一个Bean的生命周期:实例化、属性赋值、初始化、添加到一级缓存中
* 第一步:模拟实例化;获取到Class,通过反射进行实例化;
* 在Spring中在对Bean的实例化过程中还会调用工厂方法、可选有参的构造方法,这里仅仅是模拟,不要在意这些细节;
* */
final Class<?> beanClass = beanDefinition.getBeanClass();
final Object[] instance = new Object[]{beanClass.newInstance()};
/*
* 这里将回调放入回调的接口放入三级缓存中,
* 结合综合示例:Spring会在实例化并且处于循环依赖时以及实例化后调用后置处理器创建代理对象;
* 在任意节点都可以快速的进行调用,同时也可以在函数实现内拓展更多的处理,给足开发人员可拓展的机会,便于维护、拓展及使用
* */
LEVEL_Three_CACHE_MAP.put(beanName, () -> new DynamicProxyPostProcessor()
.getEarlyBeanReference(instance[0], beanName));
/*
* 第二步:开始模拟属性赋值;先获取所有的属性,通过反射进行赋值;
* 首先判断属性上是否存在@Autowired
* */
final Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
final Autowired annotation = declaredField.getAnnotation(Autowired.class);
if (annotation != null) {
/*
* 说明属性是要求注入的,这里开始属性赋值的处理;
* 先开启访问权限;递归调用本方法获取到需要注入的对象;
* 注意:在Spring中不仅可以根据名称获取,还可以根据类型以及构造函数;
* 即:byName、byType、constructor
* 这里还是不要在意这些细节,模拟而已;
* */
declaredField.setAccessible(true);
declaredField.set(instance[0], getBean(declaredField.getName()));
}
}
/*
* 第三步:有没有实现指定的方法或规则,比如init-mthod等;这里跳过吧,不需要去处理了;
* 第四步:放入一级缓存中
* */
if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
instance[0] = LEVEL_TWO_CACHE_MAP.get(beanName);
}
LEVEL_ONE_CACHE_MAP.put(beanName, instance[0]);
return instance[0];
}
执行一下看看效果吧;
PS:至于AOP方面的相关知识,在后面跟大家一起分享吧,这里就先不聊了;
五、总结
5.1)Spring是怎么解决循环依赖问题的
在Spring中是采用多级(三级)缓存的方式来处理和解决循环依赖问题的;每一级都作用于不同的类型,利于责任分工明确,便于拓展、便于维护;
注意:“一级缓存又被成为单例池”;说道这里提醒童鞋,如果面试官要问,最好带上这一句,为后面的面试问题做铺垫,因为面试官很有可能问你“Spring是如何解决多例下的循环依赖”??这个问题不知道活活坑死了多少人;
5.2)为什么要使用二级缓存和三级缓存
其实在Spring中使用一级缓存已经足够可以处理循环依赖问题,可以使用同步、可以使用锁等等一些列的手段均可实现; 仅有二级缓存也可以解决循环依赖,但是Spring作为一个生态,必须考虑拓展和维护,拓展和维护的前提是分工明确; 所以有了一个三级缓存,三级缓存主要用来存储函数回调接口对象,函数方法中调用Bean的后置处理器来完成AOP代理的实现,主要是因为解耦,让调用后置处理的逻辑可以分离出来,便于日后维护,由于调用的后置处理器,同时也给开发人员自己提供了很好的拓展支撑;
二级缓存:用于存储非完整的Bean,责任在于维护非完整的Bean,用于区分完整Bean与非完整Bean;这里其实也可以解决循环依赖问题,但是没有更好的可扩展性,原因: 获取三级缓存-----getEarlyBeanReference()经过一系列的后置处理来给我们早期对象进行特殊化处理; 从三级缓存中获取包装对象的时候 ,他会经过一次后置处理器的处理对我们早期对象的bean进行特殊化处理,但是spring的原生后置处理器没有经过处理,而是留给了我们程序员进行扩展;
PS:如果问你三级缓存是干啥的,您老人家根据自己的理解随意吧(我会回答存放代理对象及相关的知识),因为真的没有确切的定义,至少我这个菜逼没有找到;
5.3)在二级缓存中存放的是什么对象
在二级缓存中存放的不是原实例对象,而是AOP代理后的对象。原因:在A与B循环依赖的情况下,实例化A后开始进行属性赋值,在属性赋值的时候来到了B,B中A其实是通过AOP代理后的对象,递归完毕,返回A继续执行的时候A需要在二级缓存中获取一遍,这时如果拿到的还是原实例的话,原来是不完整的这里也是不完整的,永远无止境;
5.4)Spring如何解决构造函数的循环依赖
没有,因为构造函数的调用是在Bean声明周期的实例化阶段,所以这里是没有办法去处理的(老子还不存在呢,你就想查我的DNA???);
PS:但是这里需要注意的地方,您老人家认为单构造方法中如果存在这种循环依赖的情况,你自己能够初始化成功吗?那么如果在开发中非必要情况下如果使用构造方法来取代@Autowired是不是能够更加有效的规划类结构逻辑呢,您品,您细品;
5.5)Spring如何解决多例下的循环依赖
首先要先明确多例与单例的区别;多例:一个对象存在多个实例化对象;单例:全局唯一;那么多例对象不会存在于缓存对象(一级缓存容器)中,一级缓存又被成为单例池,只会存放单例对象;没有任何缓存的支撑,那必定不能解决循环依赖问题,因此Spring中遇到多例的循环依赖问题直接会抛出异常