spring对象循环依赖,关于spring循环依赖的说法错误的是
写爬虫互联网协议(互联网协议)被封了怎么解决?立即使用
首先向大家介绍下什么是循环依赖。
(学习视频分享:java视频教程)
所谓循环依赖就是A依赖b,同时B又依赖一,两者之间的依赖关系形成了一个圆环,一般是由于不正确的编码所导致春天。只能解决属性循环依赖问题,不能解决构造函数循环依赖问题,因为这个问题无解。
接下来我们首先写一个演示来演示春天是如何处理属性循环依赖问题的。
空谈不值钱。给我看看代码
第一步:定义一个类组件一,其有一个私有属性构成部分乙。
包com。技术。国际奥委会;
导入org。spring框架。豆子。工厂。注释。自动连线;
导入org。spring框架。刻板印象。组件;
/**
* @作者君战
* **/
@组件
公共类组件一个{
@自动连线
私营部门乙.
public void say(){
构成部分b。say();
}
}第二步:定义一个类成分b,其依赖成分答。并定义一个说方法便于打印数据。
包com。技术。国际奥委会;
导入org。spring框架。豆子。工厂。注释。自动连线;
导入org。spring框架。刻板印象。组件;
/**
* @作者君战
* **/
@组件
公共类组件B {
@自动连线
私人成分a;
public void say(){
System.out.println(componentA字段componentA);
系统。出去。println(这个。getclass().getName()-say());
}
}第三步:重点,编写一个类-简单容器,模仿春天底层处理循环依赖。如果理解这个代码,再去看春天处理循环依赖的逻辑就会很简单。
包com。技术。国际奥委会;
导入Java。豆子。内省者;
导入Java。郎。反思。建造师;
导入Java。郎。反思。场;
导入Java。util。hashmap
导入Java。util。地图;
导入Java。util。并发。并发hashmap
/**
* 演示春天中循环依赖是如何处理的,只是个简版,真实的春天依赖处理远比这个复杂。
* 但大体思路都相同。另外这个演示很多情况都未考虑,例如线程安全问题,仅供参考。
* @作者君战
*
* **/
公共类简单容器{
/***
* 用于存放完全初始化好的豆子,豆子处于就绪状态
* 这个地图定义和春天中一级缓存命名一致
* */
private MapString,Object singleton objects=new concurrent hashmap();
/***
* 用于存放刚创建出来的豆子,其属性还没有处理,因此存放在该缓存中的豆还不可用。
* 这个地图定义和春天中三级缓存命名一致
* */
private final MapString,Object singleton factories=new HashMap(16);
公共静态void main(String[] args) {
简单容器container=新的简单容器();
ComponentA ComponentA=容器。获取bean(ComponentA。类);
组件a。say();
}
public T T get bean(class T bean class){
字符串bean名称=this。获取bean名称(bean类);
//首先根据beanName从缓存中获取豆实例
对象bean=this。获取singleton(bean名称);
if (bean==null) {
//如果未获取到豆实例,则创建豆实例
返回createBean(beanClass,bean name);
}
返回豆类;
}
/***
* 从一级缓存和二级缓存中根据beanName来获取豆实例,可能为空
* */
私有对象getSingleton(字符串beanName) {
//首先尝试从一级缓存中获取
对象实例=单例对象。get(bean名称);
if (instance==null) { //Spring之所以能解决循环依赖问题,也是靠着这个三级缓存-单身工厂
实例=单例工厂。get(bean名称);
}
返回实例;
}
/***
* 创建指定班级的实例,返回完全状态的豆子(属性可用)
*
* */
private T T create bean(class T bean class,String beanName) {
尝试{
构造函数构造函数=bean类。getdeclaredconstructor();
t实例=构造函数。新实例();
//先将刚创建好的实例存放到三级缓存中,如果没有这一步,春天也无法解决三级缓存
单一工厂。put(bean名称、实例);
field[]fields=bean类。getdeclaredfields();
对于(字段字段:字段){
班级?字段类型=字段。gettype();
场。设置可访问性(true);
//精髓是这里又调用了依赖注入方法,例如正在处理组件答。组件b属性,
//执行到这里时就会去实例化构成部分乙。因为在依赖注入方法首先去查缓存,
//而一级缓存和三级缓存中没有成分b实例数据,所以又会调用到当前方法,
//而在处理组件乙。组件a属性时,又去调用依赖注入方法去缓存中查找,
//因为在前面我们将组件a实例放入到了三级缓存,因此可以找到。
//所以成分b的实例化结束,方法出栈,返回到实例化组件a的方法栈中,
//这时成分b已经初始化完成,因此组件答。组件b属性赋值成功!
field.set(实例,this。get bean(字段类型));
}
//最后再将初始化好的豆设置到一级缓存中。
singletonObjects.put(beanName,instance);
返回实例;
} catch(异常e) {
e。printstacktrace();
}
抛出新的IllegalArgumentException();
}
/**
* 将类名小写作为豆名,春天底层实现和这个差不多,也是使用爪哇咖啡豆的
* { @ link plain intro spector # decapitalize(String)}
**/
私有字符串getBeanName(类clazz) {
字符串clazz name=clazz。getname();
int index=clazzName.lastIndexOf( . );
字符串类名=clazz名称。子串(索引);
返回自省者。decapitalize(类名);
}
}如果各位同学已经阅读并理解上面的代码,那么接下来我们就进行真实的春天处理循环依赖问题源码分析,相信再阅读起来就会很容易。
底层源码分析
分析从AbstractBeanFactory的doGetBean方法着手。可以看到在该方法首先调用transformedBeanName(其实就是处理BeanName问题),和我们自己写的getBeanName方法作用是一样的,但春天考虑的远比这个复杂,因为有工厂豆子,别名问题。
//abstractbean factory # doget bean
受保护的T T doGetBean(
字符串名称,@ Nullable ClassT requiredType,@Nullable Object[] args,boolean typeCheckOnly)
扔豆子异常{
string bean name=转换后的bean名称(name);
对象豆
//!重点是这里,首先从缓存中beanName来获取对应的豆子。
对象共享实例=get singleton(bean名称);
if (sharedInstance!=空参数==空){
//执行到这里说明缓存中存在指定beanName的豆实例,getObjectForBeanInstance是用来处理获取到的豆是接口问题
bean=getobjectforbean实例(共享实例,名称,beanName,null);
否则{
尝试{
//删除与本次分析无关代码.
//如果是单例豆子,则通过调用创建豆方法进行创建
if (mbd.isSingleton()) {
共享实例=get singleton(bean名称,()- {
尝试{
返回createBean(beanName,mbd,args);
} catch (BeansException ex) {
销毁singleton(bean名称);
扔ex;
}
});
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(bean名称);
扔ex;
}
}
返回豆类;
}getSingleton方法存在重载方法,这里调用的是重载的getSingleton方法,注意这里传递的布尔型参数值为没错,因为该值决定了是否允许曝光早期豆子。
//defaultsingletonbean注册表# get singleton
公共对象getSingleton(字符串beanName) {
返回getSingleton(beanName,true);
}//defaultsingletonbean注册表# get singleton
受保护对象getSingleton(String beanName,boolean allowarelyreference){
//首先从一级缓存中获取
对象单例对象=this。单一对象。get(bean名称);
if(singleton object==null isSingletonCurrentlyInCreation(bean名称)){
//如果一级缓存中未获取到,再从二级缓存中获取
singleton对象=this。earlysingletonobjects。get(bean名称);
//如果未从二级缓存中获取到并且allowEarlyReference值为真(前面传的为真实)
if(singleton object==null allowarelyreference){
同步(this.singletonObjects) {
//双重检查
singleton对象=this。单一对象。get(bean名称);
if (singletonObject==null) {
singleton对象=this。earlysingletonobjects。get(bean名称);
if (singletonObject==null) {
//最后尝试去三级缓存中获取
ObjectFactory?单例工厂=这个。单一工厂。get(bean名称);
if (singletonFactory!=null) {
单一对象=单一工厂。getobject();
//保存到二级缓存
这个。earlysingletonobjects。put(bean名称,singleton对象);
//从三级缓存中移除
这个。单一工厂。删除(bean名称);
}
}
}
}
}
}
返回singletonObject
}(更多面试题请访问:java面试题及答案)
好吧,看完春天是如何从缓存中获取豆实例后,那再看看创建bean方法是如何创建豆的
受保护对象createBean(String beanName,RootBeanDefinition mbd,@Nullable Object[] args)
抛出BeanCreationException {
//删除与本次分析无关的代码.
尝试{//createBean方法底层是通过调用doCreateBean来完成豆创建的。
object bean instance=doc reate bean(bean name,mbdToUse,args);
if (logger.isTraceEnabled()) {
logger.trace(已完成创建bean beanName 的实例);
}
返回beanInstance
} catch(BeanCreationException ImplicitlyAppearedSingletonException ex){
扔ex;
} catch (Throwable ex) {
抛出新的BeanCreationException(
mbdToUse.getResourceDescription(),beanName, bean创建期间出现意外异常,ex);
}
}//abstractautowirecapablebean工厂# doCreateBean
受保护对象doCreateBean(String beanName,RootBeanDefinition mbd,@Nullable Object[] args)
抛出BeanCreationException {
bean包装实例wrapper=null
if (mbd.isSingleton()) {
实例包装=this。factorybean instancecache。删除(bean名称);
}
if (instanceWrapper==null) {
//创建豆实例
instance wrapper=create bean instance(bean name,mbd,args);
}
对象bean=实例包装器。getwrapped实例();
//如果允许当前豆早期曝光。只要豆是单例的并且允许循环引用属性为真(默认为真实)
boolean earlysingleton暴露量=(mbd。issingleton()这个。允许循环引用
isSingletonCurrentlyInCreation(bean名称));
if (earlySingletonExposure) {
//这里调用了addSingletonFactory方法将刚创建好的豆保存到了三级缓存中。
addSingletonFactory(beanName,()-getEarlyBeanReference(bean name,mbd,bean));
}
//删除与本次分析无关的代码.
对象exposedObject=bean
尝试{//Bean属性填充
populateBean(beanName,mbd,instance wrapper);
//初始化豆子,熟知的知道的接口、初始化豆接口.都是在这里调用
exposedObject=initialize bean(bean name,exposed object,mbd);
} catch (Throwable ex) {
}
//删除与本次分析无关的代码.
返回暴露的对象;
}先分析addSingletonFactory方法,因为在该方法中将豆保存到了三级缓存中。
受保护的void addSingletonFactory(字符串bean名称,ObjectFactory?singletonFactory) {
断言。not null(singletonFactory,单件工厂不得为null’);
同步(this.singletonObjects) {
//如果一级缓存中不存在指定beanName的键
如果(!这个。单一对象。包含密钥(bean名称)){
//将刚创建好的豆实例保存到三级缓存中
这个。单一工厂。put(bean名称,singleton工厂);
//从二级缓存中移除。
这个。earlysingletonobjects。删除(bean名称);
这个。注册单身。添加(bean名称);
}
}
}处理豆的依赖注入是由人口豆方法完成的,但整个执行链路太长了,这里就不展开讲了,只说下国际奥委会容器在处理依赖时是如何一步一步调用到依赖注入方法的,这样就和我们自己写的处理字段注入的逻辑对上了。
受保护的void populate bean(字符串bean名称,RootBeanDefinition mbd,@Nullable BeanWrapper bw) {
//删除与本次分析无关代码.
属性描述符[]filtered PDS=null;
if (hasInstAwareBpps) {
if (pvs==null) {
PVS=mbd。getpropertyvalues();
}
//遍历所有已注册的豆后处理器接口实现类,如果实现类是instantiationwarebeanpostprocessor接口类型的,调用其后处理属性方法。
for(BeanPostProcessor BP:getBeanPostProcessors()){
if(instantiationwarebeanpostprocessor的BP实例){
instantiationwarebeanpostprocessor IBP=(instantiationwarebeanpostprocessor)BP;
属性值pvsToUse=IBP。postprocessproperties(PVS,bw.getWrappedInstance(),bean名称);
//删除与本次分析无关代码.
pvs=pvsToUse
}
}
//删除与本次分析无关代码.
}
}在春天中,@自动连线注解是由AutowiredAnnotationBeanPostProcessor类处理,而@资源注解是由commannotationbeanpostprocessor类处理,这两个类都实现了instantiationwarebeanpostprocessor接口,都是在覆写的后处理属性方法中完成了依赖注入。这里我们就分析@自动连线注解的处理。
//AutowiredAnnotationBeanPostProcessor # postProcessProperties
公共属性值后处理属性(属性值PVS,对象bean,字符串beanName) {
//根据beanName以及豆的班级去查找豆的依赖元数据-InjectionMetadata
注入元数据metadata=find autowiringmetadata(bean name,bean.getClass(),PVS);
尝试{//调用注射方法
metadata.inject(bean,beanName,PVS);
} catch(BeanCreationException ex){
扔ex;
} catch (Throwable ex) {
抛出新BeanCreationException(bean名称,自动连线依赖项注入失败,ex);
}
返回post-vietnam syndrome 越战战后综合征
}在注入元数据的注射方法中,获取当前豆所有需要处理的依赖元素(InjectedElement),这是一个集合,遍历该集合,调用每一个依赖注入元素的注射方法。
//注入元数据#注入
public void inject(对象目标,@可空字符串beanName,@可空属性值pvs)抛出可投掷的
//获取当前豆所有的依赖注入元素(可能是方法,也可能是字段)
CollectionInjectedElement检查的元素=this。已检查的元素;
要迭代注入元素元素集合=
(已检查元素!=null?检查的元素:这个。注入元素);
如果(!elementsToIterate.isEmpty()) {
//如果当前豆的依赖注入项不为空,遍历该依赖注入元素
for(注入元素element:elementsToIterate){
//调用每一个依赖注入元素的注射方法。
element.inject(target,beanName,PVS);
}
}
}在AutowiredAnnotationBeanPostProcessor类中定义了两个内部类-AutowiredFieldElement、AutowiredMethodElement继承自注射元素,它们分别对应字段注入和方法注入。
以大家常用的字段注入为例,在AutowiredFieldElement的注射方法中,首先判断当前字段是否已经被处理过,如果已经被处理过直接走缓存,否则调用豆制品厂的已解决的依赖性方法来处理依赖。
//AutowiredAnnotationBeanPostProcessor .AutowiredFieldElement #注入
受保护的无效注射(对象bean、@可空字符串beanName、@可空属性值pvs)抛出可投掷的
Field Field=(Field)this。成员;
对象值;
if (this.cached) {//如果当前字段已经被处理过,直接从缓存中获取
value=resolvedCachedArgument(bean名称,this。cachedfieldvalue);
}否则{
//构建依赖描述符
依赖描述符desc=新的DependencyDescriptor(字段,this。必选);
desc。setcontainingclass(bean。getclass());
SetString autowiredbean names=new linked hashset(1);
Assert.state(beanFactory!=null,"没有可用的豆制品厂
类型转换器类型转换器=bean工厂。gettype converter();
尝试{//调用豆制品厂的已解决的依赖性来解析依赖
值=bean factory。已解析的依赖项(desc、beanName、autowiredBeanNames、类型转换器);
} catch (BeansException ex) {
抛出new UnsatisfiedDependencyException(null,beanName,new InjectionPoint(field),ex);
}
//删除与本次分析无关代码.
}
如果(值!=null) {
//通过反射来对属性进行赋值
ReflectionUtils.makeAccessible(字段);
field.set(bean,value);
}
}
}在DefaultListableBeanFactory实现的已解决的依赖性方法,最终还是调用doResolveDependency方法来完成依赖解析的功能。在春天源码中,如果存在做什么什么方法,那么该方法才是真正干活的方法。
//DefaultListableBeanFactory #已解析依赖项
公共对象已解析的依赖性(依赖性描述符描述符,@可空字符串请求BeanName,
@ Nullable SetString autowiredbean names,@ Nullable type converter type converter)抛出BeansException {
//.
//如果在字段(方法)上添加了@懒注解,那么在这里将不会真正的去解析依赖
object result=getautowirecadidateresolver().getlazyresolutionproxyfyfudible(
描述符,请求bean名称);
if (result==null) {
//如果未添加@懒注解,那么则调用doResolveDependency方法来解析依赖
result=doResolveDependency(descriptor,requestingBeanName,autowiredBeanNames,type converter);
}
返回结果;
}//DefaultListableBeanFactory # doresolvediency
公共对象doResolveDependency(依赖描述符描述符,@可空字符串beanName,
@ Nullable SetString autowiredbean names,@ Nullable type converter type converter)抛出BeansException {
//.
尝试{
//根据名称以及类型查找合适的依赖
MapString,对象匹配bean=findAutowireCandidates(bean名称,类型,描述符);
if (matchingBeans.isEmpty()) {//如果未找到相关依赖
if (isRequired(descriptor)) { //如果该依赖是必须的(例如@自动连线的需要属性),直接抛出异常
raiseNoMatchingBeanFound(type,descriptor.getResolvableType(),descriptor);
}
返回空
}
字符串autowiredBeanName
对象instanceCandidate
//如果查找到的依赖多于一个,例如某个接口存在多个实现类,并且多个实现类都注册到国际奥委会容器中。
if (matchingBeans.size() 1) {//决定使用哪一个实现类,@主要等方式都是在这里完成
autowiredbean name=determineAutowireCandidate(匹配bean,描述符);
if (autowiredBeanName==null) {
if (isRequired(descriptor) !指示多个豆子(类型)){
返回描述符。resolvenotunique(描述符。getresolvabletype(),匹配beans
}否则{
返回空
}
}
instanceCandidate=matching beans。get(autowiredbean名称);
}否则{
//我们正好有一个匹配。
地图EntryString,Object entry=匹配beans。条目集().迭代器()。next();
autowiredbean name=entry。getkey();
instanceCandidate=entry。getvalue();
}
if (autowiredBeanNames!=null) {
autowiredbean名称。add(autowiredbean名称);
}
//如果查找到的依赖是某个类的类别(通常如此),而不是实例,
//调用描述符的方法来根据类型resolveCandidate方法来获取该类型的实例。
if(类的instanceCandidate实例){
instanceCandidate=描述符。解析候选项(autowiredbean名称、类型、this);
}
//.
}在依赖描述符的resolveCandidate方法中,是通过调用豆制品厂的依赖注入方法来完成所依赖豆实例的获取。
//依赖描述符#解析候选项
公共对象解析候选项(字符串bean名称,类?必填类型BeanFactory beanFactory)
扔豆子异常{
返回豆制品厂。获取bean(bean名称);
}而在依赖注入方法实现中,依然是通过调用doGetBean方法来完成。这也和我们自己写的依赖处理基本一致,只不过我们自己写的比较简单,而春天要考虑和处理的场景复杂,因此代码比较繁杂,但大体思路都是一样的。
//AbstractBeanFactory#getBean
公共对象getBean(字符串名称)抛出BeansException {
返回doGetBean(name,null,null,false);
}重点是前面我们写的处理循环依赖的演示,如果理解那个代码,再看春天的循环依赖处理,就会发现很简单。
总结:
循环依赖就是指两个豆之间存在相互引用关系,例如A依赖B,B又依赖一,但春天只能解决属性循环依赖,不能解决构造函数循环依赖,这种场景也无法解决。
解决Spring循环依赖的关键是在处理Bean的属性依赖时,先将Bean存储在三级缓存中。当存在循环依赖时,从三级缓存中获取相关Bean,然后从三级缓存中移除,存储在二级缓存中,最后初始化后存储在一级缓存中。
推荐:java入门以上是java面试问题:你知道什么是循环依赖吗?Spring如何解决循环依赖?更多详情请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。