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

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

什么是循环依赖?

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

Spring的循环依赖有4种场景:

  • 构造器的循环依赖(singleton,prototype)
  • 属性的循环依赖(singleton,prototype)

「spring目前只支持singleton类型的属性循环依赖」

构造器的循环依赖

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
@Component 
public class ConstructorB { 
 
 private ConstructorA constructorA; 
 
 @Autowired 
 public ConstructorB(ConstructorA constructorA) { 
  this.constructorA = constructorA; 
 } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
@Configuration 
@ComponentScan("com.javashitang.dependency.constructor"
public class ConstructorConfig { 

  • 1.
  • 2.
  • 3.
  • 4.
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)); 
 } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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

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

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

  • 1.
  • 2.
  • 3.
  • 4.

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

属性的循环依赖

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

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
@Data 
@Component 
public class B { 
 
    @Autowired 
    private A a; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
@Configuration 
@EnableAspectJAutoProxy 
@ComponentScan("com.javashitang.dependency"
public class Config { 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
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)); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

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)); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

「在开始后面的内容的时候,我们先明确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)); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.

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

我还是把上面的例子改进一下,改成用3级缓存的实现:

public interface ObjectFactory<T> { 
    T getObject(); 

  • 1.
  • 2.
  • 3.
public class DependencyDemoV3 { 
 
    private static final Map<String, Object> singletonObjects = 
            new HashMap<>(256); 
 
    private static final Map<String, Object> earlySingletonObjects = 
            new HashMap<>(256); 
 
    private static final Map<String, ObjectFactory<?>> singletonFactories = 
            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); 
        } 
        ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); 
        if (singletonFactory != null) { 
            return (T) singletonFactory.getObject(); 
        } 
        // 实例化bean 
        Object object = beanClass.getDeclaredConstructor().newInstance(); 
        singletonFactories.put(beanName, () -> { 
            Object proxy = createProxy(object); 
            singletonFactories.remove(beanName); 
            earlySingletonObjects.put(beanName, proxy); 
            return proxy; 
        }); 
        // 开始初始化bean,即填充属性 
        Field[] fields = object.getClass().getDeclaredFields(); 
        for (Field field : fields) { 
            field.setAccessible(true); 
            // 获取需要注入字段的class 
            Class<?> fieldClass = field.getType(); 
            field.set(object, getBean(fieldClass)); 
        } 
        createProxy(object); 
        singletonObjects.put(beanName, object); 
        singletonFactories.remove(beanName); 
        earlySingletonObjects.remove(beanName); 
        return (T) object; 
    } 
 
    public static Object createProxy(Object object) { 
        // 因为这个方法有可能被执行2次,所以这里应该有个判断 
        // 如果之前提前进行过aop操作则直接返回,知道意思就行,不写了哈 
        // 需要aop的话则返回代理对象,否则返回传入的对象 
        return 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)); 
    } 
 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.

「为什么要包装一个ObjectFactory对象?」

如果创建的Bean有对应的aop代理,那其他对象注入时,注入的应该是对应的代理对象;「但是Spring无法提前知道这个对象是不是有循环依赖的情况」,而正常情况下(没有循环依赖情况),Spring都是在对象初始化后才创建对应的代理。这时候Spring有两个选择:

  • 不管有没有循环依赖,实例化后就直接创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入(只需要2级缓存,singletonObjects和earlySingletonObjects即可)
  • 「不提前创建好代理对象,在出现循环依赖被其他对象注入时,才提前生成代理对象(此时只完成了实例化)。这样在没有循环依赖的情况下,Bean还是在初始化完成才生成代理对象」(需要3级缓存)
  • 「所以到现在为止你知道3级缓存的作用了把,主要是为了正常情况下,代理对象能在初始化完成后生成,而不用提前生成」
缓存 说明
singletonObjects 第一级缓存,存放初始化完成的Bean
earlySingletonObjects 第二级缓存,存放实例化完成的Bean,有可能被进行了代理
singletonFactories 延迟生成代理对象

源码解析

获取Bean的时候先尝试从3级缓存中获取,和我们上面的Demo差不多哈!

DefaultSingletonBeanRegistry#getSingleton

当从缓存中获取不到时,会进行创建 AbstractAutowireCapableBeanFactory#doCreateBean(删除了部分代码哈)

发生循环依赖时,会从工厂里获取代理对象哈!

当开启aop代理时,SmartInstantiationAwareBeanPostProcessor的一个实现类有AbstractAutoProxyCreator

AbstractAutoProxyCreator#getEarlyBeanReference

getEarlyBeanReference方法提前进行代理,为了防止后面再次进行代理,需要用earlyProxyReferences记录一下,这个Bean已经被代理过了,不用再代理了。

AbstractAutoProxyCreator#postProcessAfterInitialization

这个方法是进行aop代理的地方,因为有可能提前代理了,所以先根据earlyProxyReferences判断一下,是否提前代理了,提前代理过就不用代理了。

当bean初始化完毕,会放入一级缓存,并从二三级缓存删除。

DefaultSingletonBeanRegistry#addSingleton

发生循环依赖时,整体的执行流程如下:

本文转载自微信公众号「Java识堂」,可以通过以下二维码关注。转载本文请联系Java识堂公众号。

 

责任编辑:武晓燕 来源: Java识堂
相关推荐

2022-03-01 18:03:06

Spring缓存循环依赖

2022-12-02 12:01:30

Spring缓存生命周期

2009-06-18 15:24:35

Hibernate二级

2009-09-24 11:04:56

Hibernate二级

2009-09-21 14:59:31

Hibernate二级

2013-09-08 23:30:56

EF Code Fir架构设计MVC架构设计

2009-06-10 15:00:58

Hibernate二级配置

2009-09-21 13:31:10

Hibernate 3

2009-09-21 14:39:40

Hibernate二级

2009-09-23 09:37:07

Hibernate缓存

2009-08-13 18:12:12

Hibernate 3

2024-12-03 14:38:07

CaffeineRedis二级缓存

2023-12-12 17:44:13

三级缓存Bean

2019-08-21 14:34:41

2023-02-26 11:15:42

缓存循环依赖

2015-06-11 10:12:26

Android图片加载缓存

2023-04-27 08:18:10

MyBatis缓存存储

2024-03-18 00:00:00

SpringBean设计

2022-05-08 19:23:28

Spring循环依赖

2023-09-26 07:49:11

AOP代理spring
点赞
收藏

51CTO技术栈公众号