Springboot源码分析之Spring循环依赖揭秘

46次阅读

共计 7385 个字符,预计需要花费 19 分钟才能阅读完成。

摘要:

若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘 Spring 循环依赖的最本质原因。

Spring 循环依赖流程图

Spring 循环依赖发生原因

  • 使用了具有代理特性的 BeanPostProcessor
  • 典型的有 事务注解 @Transactional,异步注解 @Async 等

源码分析揭秘

    protected Object doCreateBean(...){
        ...
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
        ...
    
        // populateBean 这一句特别的关键,它需要给 A 的属性赋值,所以此处会去实例化 B~~
        // 而 B 我们从上可以看到它就是个普通的 Bean(并不需要创建代理对象),实例化完成之后,继续给他的属性 A 赋值,而此时它会去拿到 A 的早期引用
        // 也就在此处在给 B 的属性 a 赋值的时候,会执行到上面放进去的 Bean A 流程中的 getEarlyBeanReference()方法  从而拿到 A 的早期引用~~
        // 执行 A 的 getEarlyBeanReference()方法的时候,会执行自动代理创建器,但是由于 A 没有标注事务,所以最终不会创建代理,so B 合格属性引用会是 A 的 ** 原始对象 **
        // 需要注意的是:@Async 的代理对象不是在 getEarlyBeanReference()中创建的,是在 postProcessAfterInitialization 创建的代理
        // 从这我们也可以看出 @Async 的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~)// 结论:此处给 A 的依赖属性字段 B 赋值为了 B 的实例(因为 B 不需要创建代理,所以就是原始对象)
        // 而此处实例 B 里面依赖的 A 注入的仍旧为 Bean A 的普通实例对象(注意  是原始对象非代理对象)注:此时 exposedObject 也依旧为原始对象
        populateBean(beanName, mbd, instanceWrapper);
        
        // 标注有 @Async 的 Bean 的代理对象在此处会被生成~~~ 参照类:AsyncAnnotationBeanPostProcessor
        // 所以此句执行完成后  exposedObject 就会是个代理对象而非原始对象了
        exposedObject = initializeBean(beanName, exposedObject, mbd);
        
        ...
        // 这里是报错的重点~~~
        if (earlySingletonExposure) {
            // 上面说了 A 被 B 循环依赖进去了,所以此时 A 是被放进了二级缓存的,所以此处 earlySingletonReference 是 A 的原始对象的引用
            //(这也就解释了为何我说:如果 A 没有被循环依赖,是不会报错不会有问题的   因为若没有循环依赖 earlySingletonReference =null 后面就直接 return 了)Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                // 上面分析了 exposedObject 是被 @Aysnc 代理过的对象,而 bean 是原始对象 所以此处不相等  走 else 逻辑
                if (exposedObject == bean) {exposedObject = earlySingletonReference;}
                // allowRawInjectionDespiteWrapping 标注是否允许此 Bean 的原始类型被注入到其它 Bean 里面,即使自己最终会被包装(代理)// 默认是 false 表示不允许,如果改为 true 表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~
                // 另外 dependentBeanMap 记录着每个 Bean 它所依赖的 Bean 的 Map~~~~
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// 我们的 Bean A 依赖于 B,so 此处值为["b"]
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    
                    // 对所有的依赖进行一一检查~    比如此处 B 就会有问题
                    //“b”它经过 removeSingletonIfCreatedForTypeCheckOnly 最终返返回 false  因为 alreadyCreated 里面已经有它了表示 B 已经完全创建完成了~~~
                    // 而 b 都完成了,所以属性 a 也赋值完成儿聊 但是 B 里面引用的 a 和主流程我这个 A 竟然不相等,那肯定就有问题(说明不是最终的)~~~
                    // so 最终会被加入到 actualDependentBeans 里面去,表示 A 真正的依赖~~~
                    for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);
                        }
                    }
        
                    // 若存在这种真正的依赖,那就报错了~~~  则个异常就是上面看到的异常信息
                    if (!actualDependentBeans.isEmpty()) {
                        throw new BeanCurrentlyInCreationException(beanName,
                                "Bean with name'" + beanName + "'has been injected into other beans [" +
                                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                "] in its raw version as part of a circular reference, but has eventually been" +
                                "wrapped. This means that said other beans do not use the final version of the" +
                                "bean. This is often the result of over-eager type matching - consider using" +
                                "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }
        ...
    }

问题简化

  • 发生循环依赖时候 Object earlySingletonReference = getSingleton(beanName, false); 肯定有值
  • 缓存工厂 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 将给实例对象添加SmartInstantiationAwareBeanPostProcessor
  • AbstractAutoProxyCreatorSmartInstantiationAwareBeanPostProcessor 的子类,一定记住了,一定记住,SmartInstantiationAwareBeanPostProcessor的子类很关键!!!!!
  • exposedObject = initializeBean(beanName, exposedObject, mbd);进行 BeanPostProcessor 后置处理,注意是BeanPostProcessor!!!!!

Spring的循环依赖被它的三级缓存给轻易解决了,但是这 2 个地方的后置处理带来了 循环依赖的问题。

对比 AbstractAdvisorAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor

由于 SmartInstantiationAwareBeanPostProcessor 的子类会在两处都会执行后置处理,所以前后都会相同的对象引用,不会发生循环依赖问题,异步注解就不行了,至于为什么?自己看上面的分析,仔细看哦!

如何解决循环依赖?

  • 改变加载顺序
  • @Lazy注解
  • allowRawInjectionDespiteWrapping设置为true(利用了判断的那条语句)
  • 别使用相关的 BeanPostProcessor 设计到的注解,,哈哈 这不太现实。

@Lazy

@Lazy一般含义是懒加载,它只会作用于BeanDefinition.setLazyInit()。而此处给它增加了一个能力:延迟处理(代理处理)

    // @since 4.0 出现得挺晚,它支持到了 @Lazy  是功能最全的 AutowireCandidateResolver
    public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
        // 这是此类本身唯一做的事,此处精析    
        // 返回该 lazy proxy 表示延迟初始化,实现过程是查看在 @Autowired 注解处是否使用了 @Lazy = true 注解 
        @Override
        @Nullable
        public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
            // 如果 isLazy=true  那就返回一个代理,否则返回 null
            // 相当于若标注了 @Lazy 注解,就会返回一个代理(当然 @Lazy 注解的 value 值不能是 false)return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
        }
    
        // 这个比较简单,@Lazy 注解标注了就行(value 属性默认值是 true)// @Lazy 支持标注在属性上和方法入参上~~~  这里都会解析
        protected boolean isLazy(DependencyDescriptor descriptor) {for (Annotation ann : descriptor.getAnnotations()) {Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
                if (lazy != null && lazy.value()) {return true;}
            }
            MethodParameter methodParam = descriptor.getMethodParameter();
            if (methodParam != null) {Method method = methodParam.getMethod();
                if (method == null || void.class == method.getReturnType()) {Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
                    if (lazy != null && lazy.value()) {return true;}
                }
            }
            return false;
        }
    
        // 核心内容,是本类的灵魂~~~
        protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,
                    "BeanFactory needs to be a DefaultListableBeanFactory");
    
            // 这里毫不客气的使用了面向实现类编程,使用了 DefaultListableBeanFactory.doResolveDependency()方法~~~
            final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
    
            //TargetSource 是它实现懒加载的核心原因,在 AOP 那一章节了重点提到过这个接口,此处不再叙述
            // 它有很多的著名实现如 HotSwappableTargetSource、SingletonTargetSource、LazyInitTargetSource、//SimpleBeanTargetSource、ThreadLocalTargetSource、PrototypeTargetSource 等等非常多
            // 此处因为只需要自己用,所以采用匿名内部类的方式实现~~~ 此处最重要是看 getTarget 方法,它在被使用的时候(也就是代理对象真正使用的时候执行~~~)TargetSource ts = new TargetSource() {
                @Override
                public Class<?> getTargetClass() {return descriptor.getDependencyType();
                }
                @Override
                public boolean isStatic() {return false;}
        
                // getTarget 是调用代理方法的时候会调用的,所以执行每个代理方法都会执行此方法,这也是为何 doResolveDependency
                // 我个人认为它在效率上,是存在一定的问题的~~~ 所以此处建议尽量少用 @Lazy~~~   
                // 不过效率上应该还好,对比 http、序列化反序列化处理,简直不值一提  所以还是无所谓  用吧
                @Override
                public Object getTarget() {Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
                    if (target == null) {Class<?> type = getTargetClass();
                        // 对多值注入的空值的友好处理(不要用 null)if (Map.class == type) {return Collections.emptyMap();
                        } else if (List.class == type) {return Collections.emptyList();
                        } else if (Set.class == type || Collection.class == type) {return Collections.emptySet();
                        }
                        throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
                                "Optional dependency not present for lazy injection point");
                    }
                    return target;
                }
                @Override
                public void releaseTarget(Object target) {}};   
    
            // 使用 ProxyFactory  给 ts 生成一个代理
            // 由此可见最终生成的代理对象的目标对象其实是 TargetSource, 而 TargetSource 的目标才是我们业务的对象
            ProxyFactory pf = new ProxyFactory();
            pf.setTargetSource(ts);
            Class<?> dependencyType = descriptor.getDependencyType();
            
            // 如果注入的语句是这么写的 private AInterface a;  那这类就是借口 值是 true
            // 把这个接口类型也得放进去(不然这个代理都不属于这个类型,反射 set 的时候岂不直接报错了吗????)if (dependencyType.isInterface()) {pf.addInterface(dependencyType);
            }
            return pf.getProxy(beanFactory.getBeanClassLoader());
        }
    }

标注有 @Lazy 注解完成注入的时候,最终注入只是一个此处临时生成的代理对象,只有在真正执行目标方法的时候才会去容器内拿到真是的 bean 实例来执行目标方法。

利用 allowRawInjectionDespiteWrapping 属性来强制改变判断

    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    }

这样会导致容器里面的是代理对象,暴露给其他实例的是原始引用,导致不生效了。由于它只对循环依赖内的 Bean 受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。

正文完
 0