欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-23 16:04:34
摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。 不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘 循环依赖的最本质原因。 Spring循环依赖流程图 Spring循环依赖发生原因 使用了具有代理特 ......

摘要:

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

spring循环依赖流程图

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

spring循环依赖发生原因

  • 使用了具有代理特性的beanpostprocessor
  • 典型的有 事务注解@transactional,异步注解@async等
    Springboot源码分析之Spring循环依赖揭秘

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

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

源码分析揭秘

    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

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

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

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

如何解决循环依赖?

  • 改变加载顺序
  • @lazy注解
  • allowrawinjectiondespitewrapping设置为true(利用了判断的那条语句)
  • 别使用相关的beanpostprocessor设计到的注解,,哈哈 这不太现实。
    Springboot源码分析之Spring循环依赖揭秘

@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受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。