Spring处理循环依赖只使用二级缓存可以吗?(一)

发布于 2022-6-2 16:39
浏览
0收藏

 

什么是循环依赖?
先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成

Spring的循环依赖有4种场景

  1. 构造器的循环依赖(singleton,prototype)
  2. 属性的循环依赖(singleton,prototype)
    「spring目前只支持singleton类型的属性循环依赖」

构造器的循环依赖

@Component
public class ConstructorA {

 private ConstructorB constructorB;

 @Autowired
 public ConstructorA(ConstructorB constructorB) {
  this.constructorB = constructorB;
 }
}
@Component
public class ConstructorB {

 private ConstructorA constructorA;

 @Autowired
 public ConstructorB(ConstructorA constructorA) {
  this.constructorA = constructorA;
 }
}
@Configuration
@ComponentScan("com.javashitang.dependency.constructor")
public class ConstructorConfig {
}
public class ConstructorMain {

 public static void main(String[] args) {
  AnnotationConfigApplicationContext context =
    new AnnotationConfigApplicationContext(ConstructorConfig.class);
  System.out.println(context.getBean(ConstructorA.class));
  System.out.println(context.getBean(ConstructorB.class));
 }
}

运行ConstructorMain的main方法的时候会在第一行就报异常,说明Spring没办法初始化所有的Bean,即上面这种形式的循环依赖Spring无法解决。

「构造器的循环依赖,可以在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入」

@Autowired
public ConstructorB(@Lazy ConstructorA constructorA) {
 this.constructorA = constructorA;
}

因为我们主要关注属性的循环依赖,构造器的循环依赖就不做过多分析了

属性的循环依赖
先演示一下什么是属性的循环依赖

@Data
@Component
public class A {

    @Autowired
    private B b;
}
@Data
@Component
public class B {

    @Autowired
    private A a;
}
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.javashitang.dependency")
public class Config {
}
public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);
        System.out.println(context.getBean(A.class).getB() == context.getBean(B.class));
        System.out.println(context.getBean(B.class).getA() == context.getBean(A.class));
    }
}

Spring容器正常启动,运行结果为true,想实现类似的功能并不难,我写个demo演示一下

public class DependencyDemoV1 {

    private static final Map<String, Object> singletonObjects =
            new HashMap<>(256);

    @SneakyThrows
    public static <T> T getBean(Class<T> beanClass) {
        String beanName = beanClass.getSimpleName();
        if (singletonObjects.containsKey(beanName)) {
            return (T) singletonObjects.get(beanName);
        }
        // 实例化bean
        Object object = beanClass.getDeclaredConstructor().newInstance();
        singletonObjects.put(beanName, object);
        // 开始初始化bean,即填充属性
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            // 获取需要注入字段的class
            Class<?> fieldClass = field.getType();
            field.set(object, getBean(fieldClass));
        }
        return (T) object;
    }

    public static void main(String[] args) {
        // 假装扫描出来的类
        Class[] classes = {A.class, B.class};
        for (Class aClass : classes) {
            getBean(aClass);
        }
        System.out.println(getBean(A.class).getB() == getBean(B.class));
        System.out.println(getBean(B.class).getA() == getBean(A.class));
    }

}

 

「在开始后面的内容的时候,我们先明确2个概念」

实例化:调用构造函数将对象创建出来 初始化:调用构造函数将对象创建出来后,给对象的属性也被赋值

可以看到只用了一个map就实现了循环依赖的实现,但这种实现有个小缺陷,singletonObjects中的类有可能只是完成了实例化,并没有完成初始化

而在spring中singletonObjects中的类都完成了初始化,因为我们取单例Bean的时候都是从singletonObjects中取的,不可能让我们获取到没有初始化完成的对象。

所以我们来写第二个实现,「用singletonObjects存初始化完成的对象,而用earlySingletonObjects暂存实例化完成的对象,等对象初始化完毕再将对象放入singletonObjects,并从earlySingletonObjects删除」

public class DependencyDemoV2 {

    private static final Map<String, Object> singletonObjects =
            new HashMap<>(256);

    private static final Map<String, Object> earlySingletonObjects =
            new HashMap<>(256);

    @SneakyThrows
    public static <T> T getBean(Class<T> beanClass) {
        String beanName = beanClass.getSimpleName();
        if (singletonObjects.containsKey(beanName)) {
            return (T) singletonObjects.get(beanName);
        }
        if (earlySingletonObjects.containsKey(beanName)) {
            return (T) earlySingletonObjects.get(beanName);
        }
        // 实例化bean
        Object object = beanClass.getDeclaredConstructor().newInstance();
        earlySingletonObjects.put(beanName, object);
        // 开始初始化bean,即填充属性
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            // 获取需要注入字段的class
            Class<?> fieldClass = field.getType();
            field.set(object, getBean(fieldClass));
        }
        singletonObjects.put(beanName, object);
        earlySingletonObjects.remove(beanName);
        return (T) object;
    }

    public static void main(String[] args) {
        // 假装扫描出来的类
        Class[] classes = {A.class, B.class};
        for (Class aClass : classes) {
            getBean(aClass);
        }
        System.out.println(getBean(A.class).getB() == getBean(B.class));
        System.out.println(getBean(B.class).getA() == getBean(A.class));
    }

}

 

现在的实现和spring保持一致了,并且只用了2级缓存。spring为什么搞第三个缓存呢?「第三个缓存主要和代理对象相关」

 

文章转自公众号:Java识堂

分类
标签
已于2022-6-2 16:39:22修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐