Spring ⑦ 循环引用问题

spring-logo-0.jpg

Spring ⑦ 循环引用问题

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

Bean 的实例化初始化顺序

场景
public class CircularReferenceExp1 {

    public static void main(String[] args) {

        // 循环引用实验

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(factory);
        factory.addBeanPostProcessor(processor);

        factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean1.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean2.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean3" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean3.class)
                .getBeanDefinition());

        Bean3 bean3 = factory.getBean(Bean3.class);
        System.out.println("bean1 -> " + bean3);
    }

    static class Bean1 {

        @Autowired
        Bean3 bean3;

        public Bean1() {
            System.out.println("======================== bean1 实例化");
        }

    }

    static class Bean2 {

        @Autowired
        Bean1 bean1;

        public Bean2() {
            System.out.println("++++++++++++++++++++++++++ bean2 实例化");
        }
    }

    static class Bean3 {

        @Autowired
        Bean2 bean2;

        public Bean3() {
            System.out.println("++++++++++++++++++++++++++ Bean3 实例化");
        }
    }
}
运行日志输出:
20:56:41.417 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
++++++++++++++++++++++++++ Bean3 实例化
20:56:41.476 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
++++++++++++++++++++++++++ bean2 实例化
20:56:41.477 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
======================== bean1 实例化
bean1 -> org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp1$Bean3@22635ba0

spring 在创建 Bean 的过程中是从外到内的去创建 Bean 。在解析 Bean 中依赖项的时候便会去实例化该依赖项对应的 Bean 并完成 spring 对 Bean 的初始化发布过程。

image.png

构造器依赖注入时循环引用

先说结论构造器依赖注入时存在循环引用的情况下会导致 Bean 创建失败抛出异常. 要想知道原因只要理解了构造器依赖注入与属性、方法依赖注入的区别是什么就很容易理解了。构造器依赖注入时需要先找到依赖项才能实例化当前 Bean ,属性和方法依赖注入时是先实例化当前 Bean 再去寻找依赖项实例。

image.png

场景
public class CircularReferenceExp2 {

    public static void main(String[] args) {

        // 循环引用实验

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(factory);
        factory.addBeanPostProcessor(processor);

        factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean1.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean2.class)
                .getBeanDefinition());


        Bean1 bean1 = factory.getBean(Bean1.class);
        System.out.println("bean1 -> " + bean1);
    }

    static class Bean1 {

        final Bean2 bean2;

        public Bean1(Bean2 bean2) {
            this.bean2 = bean2;
            System.out.println("======================== bean1 实例化 , bean2 is = " + bean2);
        }

    }

    static class Bean2 {

        final Bean1 bean1;

        public Bean2(Bean1 bean1) {
            this.bean1 = bean1;
            System.out.println("++++++++++++++++++++++++++ bean2 实例化 , bean1 is = " + bean1);
        }
    }

}

观察输出的日志发现,输出了两次 Creating shared instance of singleton bean 'bean1' , 在创建 bean2 的过程中因为工厂内还没有 bean1 所以又去创建了一次. 在第二次创建 bean1 的过程中, spring 发现 bean1 其实正在被创建的过程中(首次创建 bean1 的过程), 所以 spring 抛出了 BeanCurrentlyInCreationException 异常。 假设如果 spring 允许第二次创建 bean1 成功的话, 那么首次创建 bean1 的过程也可以成功.这样工厂中就会存在两个 bean1 实例,或者是只存在一个其中一个被覆盖,那么 bean2 中究竟应该持有那个 bean1 的实例呢?问题变得更为复杂了.

image.png

运行日志输出:
21:23:06.516 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
21:26:01.837 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
21:29:03.383 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean1': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean2': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1282)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1243)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp2.main(CircularReferenceExp2.java:32)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bean2': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 15 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 29 more

属性方法依赖注入时循环引用

在前面内容中已经介绍了属性、方法注入与构造器注入时循环引用问题的区别,接下来重点讲解在属性、方法注入时 spring 如何处理循环引用问题。

场景
public class CircularReferenceExp3 {

    public static void main(String[] args) {

        // 循环引用实验
        
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger logger = loggerContext.getLogger(DefaultListableBeanFactory.class);
        logger.setLevel(Level.TRACE);

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(factory);
        factory.addBeanPostProcessor(processor);

        factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean1.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean2.class)
                .getBeanDefinition());


        Bean1 bean1 = factory.getBean(Bean1.class);
        System.out.println("bean1 -> " + bean1);
    }

    static class Bean1 {

        @Autowired
        Bean2 bean2;

        public Bean1() {
            System.out.println("======================== bean1 实例化 ");
        }

    }

    static class Bean2 {

        @Autowired
        Bean1 bean1;

        public Bean2() {
            System.out.println("++++++++++++++++++++++++++ bean2 实例化 ");
        }
    }

}
运行日志输出:

从输入的日志中可以观察到在创建了实列对象 ( Creating shared instance ) 后,该实例就被添加到了缓存当中 ( Eagerly caching bean ) . 目的就是为了解决可能存在的循环引用场景。而 bean2 在注入 bena1 的时候获取的 bean1 是从缓存中获取的实例对象 ( Returning eagerly cached instance of singleton bean ).

17:34:34.328 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
17:34:34.332 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'bean1'
======================== bean1 实例化 
17:34:34.385 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'bean1' to allow for resolving potential circular references
17:34:34.390 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
17:34:34.390 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'bean2'
++++++++++++++++++++++++++ bean2 实例化 
17:34:34.391 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Eagerly caching bean 'bean2' to allow for resolving potential circular references
17:34:34.391 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning eagerly cached instance of singleton bean 'bean1' that is not fully initialized yet - a consequence of a circular reference
17:34:34.393 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'bean2'
17:34:34.393 [main] TRACE org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean 'bean1'
bean1 -> org.devx.spring.certified.professional.edu.hp.experiment.CircularReferenceExp3$Bean1@22635ba0

从图中分析出在循环引用时注入的依赖 bean 是刚刚完成创建过程的 bean 并没有完成整体的初始化过程.因为在没有执行 populateBean 方法和 initializeBean 就被放入了 singletonFactories 缓存当中.也就是说这时候注入的 bean 实例并没有经过 BeanPostProcessor 的处理, 也没有执行 InitializingBean afterPropertiesSet 方法. 其实这时候被放入 singletonFactories 缓存中的是一个 ObjectFactory 的匿名内部类其中指向了 AbstractAutowireCapableBeanFactory#etEarlyBeanReference 方法。其中会去调用 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 方法, 比如 AnnotationAwareAspectJAutoProxyCreator 就保证了我们获取到的是一个代理对象。下面会附上一些关键部分源码帮助读者理解图示.

扫描二维码关注公众号,回复: 16141983 查看本文章

属性和方法注入循环引用.drawio.png

getSingleton
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
         synchronized (this.singletonObjects) {
            // Consistent creation of early reference within full singleton lock
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}
doCreateBean

image.png

addSingletonFactory
/**
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}
getEarlyBeanReference
/**
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @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;
   if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
         exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
      }
   }
   return exposedObject;
}
addSingleton

在 bean 完成了整个创建和初始化过程后 , bean 会被加入到 singletonObjects 缓存中,并从 singletonFactories earlySingletonObjects 缓存中清除.

/**
 * Add the given singleton object to the singleton cache of this factory.
 * <p>To be called for eager registration of singletons.
 * @param beanName the name of the bean
 * @param singletonObject the singleton object
 */
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}

到这里 spring 是如何处理循环引用的原理我们就讲完了,接下来看如果在使用过程中出现了循环引用该如何解决. (最好的办法就是不要循环引用,这个就涉及到项目结构设计,编码规范等等方面,这里并不展开。).

解决循环引用

解决循环引用的思路是 延迟加载 让 bean 在真正被使用时再去加载,这样做的缺点是会有一点性能损耗一般情况下可以忽略不计. 可以通过使用 @Lazy 注解的方式或者注入 ObjectFactory ObjectProvider jsr-330 Provider 的方式实现懒加载.

属性和方法注入循环引用.drawio (1).png

总结

本章讲解了 构造器依赖注入时循环引用 属性方法依赖注入时循环引用 的场景。在构造器注入时得到的依赖项是经过了完整的初始化过程的,可以说是得到的一个完整的 bean 。而属性或方法注入的场景得到的 bean 是没有经过完全初始化的,所以在使用 bean 时需要警惕.

抱歉最近没有更新因为我在追剧,我最近在考虑要不要转行卖鱼了 : )

image.png


DevX 会持续分享 Java 技术干货,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【 DevXJava 】, 方便在微信客户端阅读。

猜你喜欢

转载自blog.csdn.net/weixin_45839894/article/details/129042079
今日推荐