怎么检查外包做的网站,html手机网站模板,wordpress清新Minty主题,侠客推 网站建设1.由同事抛的一个问题开始 
最近项目组的一个同事遇到了一个问题#xff0c;问我的意见#xff0c;一下子引起的我的兴趣#xff0c;因为这个问题我也是第一次遇到。平时自认为对spring循环依赖问题还是比较了解的#xff0c;直到遇到这个和后面的几个问题后#xff0c;重…1.由同事抛的一个问题开始 
最近项目组的一个同事遇到了一个问题问我的意见一下子引起的我的兴趣因为这个问题我也是第一次遇到。平时自认为对spring循环依赖问题还是比较了解的直到遇到这个和后面的几个问题后重新刷新了我的认识。 
我们先看看当时出问题的代码片段 
Service
public class TestService1 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {}
}Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
}这两段代码中定义了两个Service类TestService1和TestService2在TestService1中注入了TestService2的实例同时在TestService2中注入了TestService1的实例这里构成了循环依赖。 
只不过这不是普通的循环依赖因为TestService1的test1方法上加了一个Async注解。 
大家猜猜程序启动后运行结果会怎样 
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name testService1:Bean with name testService1 has been injected into other beans [testService2] 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.报错了。。。原因是出现了循环依赖。 
「不科学呀spring不是号称能解决循环依赖问题吗怎么还会出现」 
如果把上面的代码稍微调整一下 
Service
public class TestService1 {Autowiredprivate TestService2 testService2;public void test1() {}
}把TestService1的test1方法上的Async注解去掉TestService1和TestService2都需要注入对方的实例同样构成了循环依赖。 
但是重新启动项目发现它能够正常运行。这又是为什么 
带着这两个问题让我们一起开始spring循环依赖的探秘之旅。 
2.什么是循环依赖 
循环依赖说白是一个或多个对象实例之间存在直接或间接的依赖关系这种依赖关系构成了构成一个环形调用。 
第一种情况自己依赖自己的直接依赖 第二种情况两个对象之间的直接依赖 第三种情况多个对象之间的间接依赖 前面两种情况的直接循环依赖比较直观非常好识别但是第三种间接循环依赖的情况有时候因为业务代码调用层级很深不容易识别出来。 
3.循环依赖的N种场景 
spring中出现循环依赖主要有以下场景 单例的setter注入 
这种注入方式应该是spring用的最多的代码如下 
Service
public class TestService1 {Autowiredprivate TestService2 testService2;public void test1() {}
}Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
}这是一个经典的循环依赖但是它能正常运行得益于spring的内部机制让我们根本无法感知它有问题因为spring默默帮我们解决了。 
spring内部有三级缓存 
singletonObjects 一级缓存用于保存实例化、注入、初始化完成的bean实例earlySingletonObjects 二级缓存用于保存实例化完成的bean实例singletonFactories 三级缓存用于保存bean创建工厂以便于后面扩展有机会创建代理对象。 
下面用一张图告诉你spring是如何解决循环依赖的 图1 
细心的朋友可能会发现在这种场景中第二级缓存作用不大。 
那么问题来了为什么要用第二级缓存呢 
试想一下如果出现以下这种情况我们要如何处理 
Service
public class TestService1 {Autowiredprivate TestService2 testService2;Autowiredprivate TestService3 testService3;public void test1() {}
}Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
}Service
public class TestService3 {Autowiredprivate TestService1 testService1;public void test3() {}
}TestService1依赖于TestService2和TestService3而TestService2依赖于TestService1同时TestService3也依赖于TestService1。 
按照上图的流程可以把TestService1注入到TestService2并且TestService1的实例是从第三级缓存中获取的。 
假设不用第二级缓存TestService1注入到TestService3的流程如图 图2 
TestService1注入到TestService3又需要从第三级缓存中获取实例而第三级缓存里保存的并非真正的实例对象而是ObjectFactory对象。说白了两次从三级缓存中获取都是ObjectFactory对象而通过它创建的实例对象每次可能都不一样的。 
这样不是有问题 
为了解决这个问题spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了而在TestService1注入到TestService3时只用从第二级缓存中获取该对象即可。 图3 
还有个问题第三级缓存中为什么要添加ObjectFactory对象直接保存实例对象不行吗 
答不行因为假如你想对添加到三级缓存中的实例对象进行增强直接用实例对象是行不通的。 
针对这种场景spring是怎么做的呢 
答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中 它定义了一个匿名内部类通过getEarlyBeanReference方法获取代理对象其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。 
多例的setter注入 
这种注入方法偶然会有特别是在多线程的场景下具体代码如下 
Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Service
public class TestService1 {Autowiredprivate TestService2 testService2;public void test1() {}
}Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
}很多人说这种情况spring容器启动会报错其实是不对的我非常负责任的告诉你程序能够正常启动。 
为什么呢 
其实在AbstractApplicationContext类的refresh方法中告诉了我们答案它会调用finishBeanFactoryInitialization方法该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了preInstantiateSingletons方法 标红的地方明显能够看出非抽象、单例 并且非懒加载的类才能被提前初始bean。 
而多例即SCOPE_PROTOTYPE类型的类非单例不会被提前初始化bean所以程序能够正常启动。 
如何让他提前初始化bean呢 
只需要再定义一个单例的类在它里面注入TestService1 
Service
public class TestService3 {Autowiredprivate TestService1 testService1;
}重新启动程序执行结果 
Requested bean is currently in creation: Is there an unresolvable circular reference?果然出现了循环依赖。 
注意这种循环依赖问题是无法解决的因为它没有用缓存每次都会生成一个新对象。 
构造器注入 
这种注入方式现在其实用的已经非常少了但是我们还是有必要了解一下看看如下代码 
Service
public class TestService1 {public TestService1(TestService2 testService2) {}
}Service
public class TestService2 {public TestService2(TestService1 testService1) {}
} 
运行结果 
Requested bean is currently in creation: Is there an unresolvable circular reference?出现了循环依赖为什么呢 从图中的流程看出构造器注入没能添加到三级缓存也没有使用缓存所以也无法解决循环依赖问题。 
单例的代理对象setter注入 
这种注入方式其实也比较常用比如平时使用Async注解的场景会通过AOP自动生成代理对象。 
我那位同事的问题也是这种情况。 
Service
public class TestService1 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {}
}Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
}从前面得知程序启动会报错出现了循环依赖 
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean 
with name testService1: Bean with name testService1 has been injected into other 
beans [testService2] 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.为什么会循环依赖呢 
答案就在下面这张图中 说白了bean初始化完成之后后面还有一步去检查第二级缓存 和 原始对象 是否相等。由于它对前面流程来说无关紧要所以前面的流程图中省略了但是在这里是关键点我们重点说说 那位同事的问题正好是走到这段代码发现第二级缓存 和 原始对象不相等所以抛出了循环依赖的异常。 
如果这时候把TestService1改个名字改成TestService6其他的都不变。 
Service
public class TestService6 {Autowiredprivate TestService2 testService2;Asyncpublic void test1() {}
} 
再重新启动一下程序神奇般的好了。 
what 这又是为什么 
这就要从spring的bean加载顺序说起了默认情况下spring是按照文件完整路径递归查找的按路径文件名排序排在前面的先加载。所以TestService1比TestService2先加载而改了文件名称之后TestService2比TestService6先加载。 
为什么TestService2比TestService6先加载就没问题呢 
答案在下面这张图中 这种情况testService6中其实第二级缓存是空的不需要跟原始对象判断所以不会抛出循环依赖。 
DependsOn循环依赖 
还有一种有些特殊的场景比如我们需要在实例化Bean A之前先实例化Bean B这个时候就可以使用DependsOn注解。 
DependsOn(value  testService2)
Service
public class TestService1 {Autowiredprivate TestService2 testService2;public void test1() {}
}DependsOn(value  testService1)
Service
public class TestService2 {Autowiredprivate TestService1 testService1;public void test2() {}
} 
程序启动之后执行结果 
Circular depends-on relationship between testService2 and testService1这个例子中本来如果TestService1和TestService2都没有加DependsOn注解是没问题的反而加了这个注解会出现循环依赖问题。 
这又是为什么 
答案在AbstractBeanFactory类的doGetBean方法的这段代码中 它会检查dependsOn的实例有没有循环依赖如果有循环依赖则抛异常。 
4.出现循环依赖如何解决 
项目中如果出现循环依赖问题说明是spring默认无法解决的循环依赖要看项目的打印日志属于哪种循环依赖。目前包含下面几种情况 生成代理对象产生的循环依赖 
这类循环依赖问题解决方法很多主要有 
使用Lazy注解延迟加载使用DependsOn注解指定加载先后关系修改文件名称改变循环依赖类的加载顺序 
使用DependsOn产生的循环依赖 
这类循环依赖问题要找到DependsOn注解循环依赖的地方迫使它不循环依赖就可以解决问题。 
多例循环依赖 
这类循环依赖问题可以通过把bean改成单例的解决。 
构造器循环依赖 
这类循环依赖问题可以通过使用Lazy注解解决。 
转载https://www.zhihu.com/question/438247718/answer/1730527725看评论