先不去管Spring中的循环依赖,我们先实现一个自定义注解,来模拟@Autowired的功能。
一、自定义注解模拟@Autowired
自定义Load注解,被该注解标识的字段,将会进行自动注入
/**
* @author qcy
* @create 2021/10/02 13:31:20
*/
//只用在字段上
@Target(ElementType.FIELD)
//运行时有效,这样可以通过反射解析注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Load {
}
新建A类与B类,其中A类中需要注入B
public class A {
@Load
private B b;
public B getB() {
return b;
}
}
public class B {
}
测试类
public class Main {
private static <T> T getBean(Class<T> clazz) throws IllegalAccessException, InstantiationException {
//实例化对象
T instance = clazz.newInstance();
//获取当前类中的所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//允许访问私有变量
field.setAccessible(true);
//判断字段是否被@Load注解修饰
boolean isUseLoad = field.isAnnotationPresent(Load.class);
if (!isUseLoad) {
continue;
}
//获取需要被注入的字段的class
Class<?> fieldType = field.getType();
//递归获取字段的实例对象
Object fieldBean = getBean(fieldType);
//将实例对象注入到该字段中
field.set(instance, fieldBean);
}
return instance;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
A a = getBean(A.class);
System.out.println(a.getB().getClass());
}
最终能够打印出a对象中依赖的b的class类型

二、多例模式下的循环依赖
现在思考一个问题,如果b对象同时依赖a呢?也就是B类中需要注入A
现在直接把B类的代码改成以下的样子
public class B {
@Load
private A a;
public A getA() {
return a;
}
}
直接运行测试类,会发生什么呢?

出现了栈溢出!到底是哪里出问题了呢?

原来是,在实例化A后,属性注入阶段发现需要注入B的实例,于是去实例化B,B又需要依赖A,因此去实例化A,一直依赖下去...

不难观察出,getBean每调用一次,都会返回一个新的对象,也就是对应于多例模式。
多例模式中出现循环依赖,直接报出了StackOverflowError,看来解决不了循环依赖,这也并不难理解
三、单例模式下使用缓存来解决循环依赖
如果这个时候,对于传入的同一个class,能够返回同一个实例,即单例模式,能否解决循环依赖呢?
大致的思路是,使用一个缓存map,将实例化好且属性注入完毕的对象缓存到该map中,下次直接使用即可。
可现在又遇到难题了,压根就创建不出来一个完整的A的实例对象啊,无法进行缓存。
既然无法直接将完成品放入到缓存中,那是否可以将实例对象分为两个阶段
半成品阶段
仅完成实例化,并没有完成属性注入
成品阶段
半成品完成属性注入
首先实例化A得到半成品a,接着将这个a放入到缓存中。然后实例化b时,注入缓存中半成品的a,得到成品b。最终再将成品b注入到半成品a中,此时a变为成品。
这个时候,a完成了实例化与属性注入,b也完成了实例化与属性注入,循环依赖好像就能解决了。

改下代码,直接上线!
public class Main {
//由类名可以获取到对应的实例对象
private static Map<String, Object> singletonObjects= new HashMap<>();
private static <T> T getBean(Class<T> clazz) throws IllegalAccessException, InstantiationException {
//先从缓存中获取
String className = clazz.getSimpleName();
if (singletonObjects.containsKey(className)) {
return (T) singletonObjects.get(className);
}
//实例化对象
T instance = clazz.newInstance();
//实例化完成后,就将这个半成品放入到缓存中
singletonObjects.put(className

本文通过实例分析了Spring中如何解决单例模式下的循环依赖问题,介绍了使用两级缓存解决多线程环境下的安全性问题,并探讨了Spring的三级缓存机制及其作用,揭示了解决循环依赖的本质——借助于缓存。同时也讨论了构造器注入与set注入在循环依赖处理上的差异。
最低0.47元/天 解锁文章
9万+

被折叠的 条评论
为什么被折叠?



