前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >spring如何解决循环依赖

spring如何解决循环依赖

作者头像
科技新语
发布于 2024-08-16 09:43:29
发布于 2024-08-16 09:43:29
9700
代码可运行
举报
运行总次数:0
代码可运行

循环依赖

spring中将循环依赖处理分为了两种情况

构造器循环依赖

使用构造器注入构成循环依赖,这种方式无法进行解决,抛出了BeanCurrentlyInCreationException异常

在创建bean之前会进行检测

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码protected void beforeSingletonCreation(String beanName) {
  // inCreationCheckExclusions中是否存在当前正在创建的bean
  // 并且singletonsCurrentlyInCreation是否可以添加成功(也就是singletonsCurrentlyInCreation中是否存在正在创建的bean)
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

在创建bean之后会进行移除

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码protected void afterSingletonCreation(String beanName) {
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
      throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
   }
}

以TestA和TestB为例,由于构造器造成了循环依赖,所以在进行实例化TestA时(实例化TestA之前执行了beforeSingletonCreation方法,此时singletonsCurrentlyInCreation中添加TestA),需要TestB实例,所以需要实例化TestB(实例化TestB之前执行了beforeSingletonCreation方法,此时singletonsCurrentlyInCreation中添加TestB),然后在进行TestB时需要TestA实例,又需要进行实例化TestA,但是执行beforeSingletonCreation方法时,singletonsCurrentlyInCreation中已存在TestA,所以会抛出BeanCurrentlyInCreationException异常

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码public class TestA {
    
    private TestB testB;
    
    public TestA(TestB testB){
        this.testB = testB;
    }
}

public class TestB {
    private TestA testA;

    public TestB(TestA testA){
        this.testA = testA;
    }
}

setter循环依赖

对于setter注入造成的循环依赖,spring采用的是提前暴露刚完成的构造器实例化但未完成setter方法注入的bean来实现的,而且只能解决单例作用域的bean

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码// ①doGetBean
// 获取bean
// allowEarlyReference是true,表示允许早期依赖
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从singletonObjects缓存中取
  // singletonObjects中存的是beanName和bean实例
  // singletonObjects是一级缓存
   Object singletonObject = this.singletonObjects.get(beanName);
  // 缓存中没有
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
     // 锁定全局变量进行处理
      synchronized (this.singletonObjects) {
        // 如果该bean正在进行加载则不进行处理,直接返回
        // earlySingletonObjects中存的是beanName和bean实例,但是与singletonObjects不同的是,存储的是还没进行属性注入操作的Bean的实例,,目的是为了监测循环引用
        // earlySingletonObjects是二级缓存
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
           // 从singletonFactories中获取,对于某些方法需要提前进行初始化时则会调用addSingletonFactory方法将对应的ObjectFactory初始化策略存在singletonFactories中
           // singletonFactories中存的是beanName和创建bean的ObjectFactory工厂
           // singletonFactories是三级缓存
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
              // 调用ObjectFactory的getObject方法
               singletonObject = singletonFactory.getObject();
              // 放入到二级缓存中
              // 记录在缓存earlySingletonObjects中,earlySingletonObjects和singletonFactories互斥,earlySingletonObjects与singletonObjects的不同之处在于当一个单例bean被放入该缓存后,那么其他的bean在创建过程中就能通过getBean方法获取到,目的是用来监测循环引用
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

// ②doGetBean
// 第一次创建,缓存中不存在,则会进行创建
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
  @Override
  public Object getObject() throws BeansException {
    try {
      // 创建bean
      return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
      // Explicitly remove instance from singleton cache: It might have been put there
      // eagerly by the creation process, to allow for circular reference resolution.
      // Also remove any beans that received a temporary reference to the bean.
      destroySingleton(beanName);
      throw ex;
    }
  }
});

// ③doCreateBean
if (instanceWrapper == null) {
  // 创建bean实例
   instanceWrapper = createBeanInstance(beanName, mbd, args);
}

// ④
addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
});

// 将ObjectFactory加入到三级缓存中
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);
			}
		}
	}
// ⑤
// 调用applyPropertyValues(beanName, mbd, bw, pvs)来set值
populateBean(beanName, mbd, instanceWrapper);

// ⑥
// 整个bean实例创建并且属性注入后执行
// if (newSingleton) {
//					addSingleton(beanName, singletonObject);
//				}
protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
      // 加入一级缓存
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
      // 移除二级缓存
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

以TestA和TestB为例,创建单例TestA时,使用无参构造器进行创建,并将实例化的bean放入一个ObjectFactory中,存入到singletonFactories,用于返回提前暴露的bean,然后进行setter方法来注入TestB,此时还没有TestB,来创建单例TestB,使用无参构造器进行创建,并将实例化的bean放入一个ObjectFactory中,存入到singletonFactories,用于返回提前暴露的bean,然后进行setter来注入TestA,从ObjectFactory.getObject可以获取到testA,完成了TestB#setTestA方法,之后继续走TestA的逻辑,从而完成TestA#setTestB方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码public class TestA {
    
    private TestB testB;

    public TestB getTestB() {
        return testB;
    }

    public void setTestB(TestB testB) {
        this.testB = testB;
    }
}

public class TestB {
    private TestA testA;

    public TestA getTestA() {
        return testA;
    }

    public void setTestA(TestA testA) {
        this.testA = testA;
    }
}
总结一下
  • 一级缓存 singletonObjects 存放的是完全创建好的单例Bean
  • 二级缓存 earlySingletonObjects 存放的是完成实例化,但是还未进行属性注入的对象
  • 三级缓存 singletonFactories 提前暴露的一个单例工厂,二级缓存的对象就是通过这个单例工厂创建的
有个疑问

看上去好像二级缓存就可以解决循环依赖了,三级缓存的对象工厂的意义是什么呢?

来看一下不管有没有循环依赖,都会创建好一个对象,然后放入到三级缓存中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码// 加入三级缓存
addSingletonFactory(beanName, new ObjectFactory<Object>() {
   @Override
   public Object getObject() throws BeansException {
      return getEarlyBeanReference(beanName, mbd, bean);
   }
});

只有调用三级缓存中的ObjectFactory.getObject()才会拿到对象,看一下getEarlyBeanReference干了什么吧

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java 代码解读复制代码protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
           // 使用后置处理器,这里做了个代理,也就是AOP  (AbstractAutoProxyCreator继承的SmartInstantiationAwareBeanPostProcessor,也就是在getEarlyBeanReference方法中调用的wrapIfNecessary方法)
            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            if (exposedObject == null) {
               return null;
            }
         }
      }
   }
   return exposedObject;
}

也就是做了个延迟代理的作用,在二级缓存里存放的其实是代理过的对象

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
怎么自定义服务器的404,如何自定义404页面
404错误页面是WWW网站访问比较经常出现的错误。大家最熟悉的也是最常见的出错提示:404 not found。404页面就是当用户输入了错误的链接时,返回的页面。而默认的404错误页面呆板麻木,让访问者感觉很挫败,可能会直接离开您的网站。
全栈程序员站长
2022/09/18
1.6K0
网站的404错误页面制作方法[通俗易懂]
大家好,又见面了,我是你们的朋友全栈君。   网站的404错误页面怎么做?   网站的404错误页面怎么做呢?很多人学习完seo就对404错误页面的理解有所偏差,认为404错误页面只是为了SEO而
全栈程序员站长
2022/09/18
2.2K0
不懂代码,如何制作漂亮的404页面【新手简易教程】
404是用户在访问页面时,搜索引擎常返回的状态码,常见的还有200,301,302,500等。搜索引擎通过http状态码识别网页状态,404状态码,常指所访问的页面不存在或已被删除。
奔跑的小鹿
2020/01/13
1.8K0
不懂代码,如何制作漂亮的404页面【新手简易教程】
网站404页面怎么做
谈及网站404页面,诸多站长在网站运营的过程中,难以避免404页面的出现。404页面意味着网站存在死链接,当搜索引擎蜘蛛爬取此类网址的时候,应当反馈404状态吗,告知此URL网址已经失效,搜索引擎蜘蛛便会回馈到数据中心,从中清除数据。而在页面的前端,应该对访客予以信息提示,告知内容的已经不存在,优化相关搜索内容,尽可能避免降低访客体验度,由此可见,网站404页面的制作应当以两种出发点进行,即对于搜索引擎蜘蛛状态吗的正确回馈和对前端界面及内容设计出发。
全栈程序员站长
2022/09/18
1.5K0
网站404页面怎么做
星辰云同款超级好看404页面源码
下载地址:https://url99.ctfile.com/f/34816699-534147500-45782d
若梦
2022/04/01
1.1K0
处理死链一「建议收藏」
这也是许多网站使用自定义404错误页面的原因。通过良好的自定义404 页面,可以包含对网站的相应介绍、用户可能感兴趣的内容链接或者网站内容导航链接、内容搜索功能等,能够有效地帮助访问者找到其欲寻找的内容或相似的内容,提高用户在网站内浏览更多信息的机会。   正确定义404错误页面:   1. 对于已经存在的信息由于路径改变而导致访问不了时,应该在IIS 中定义404错误指向一个动态页面,在页面里面使用301 跳转到新的地址,此时服务器返回301状态码。   2. 当访问一个错误的链接时,将调用404页面,但由于在IIS 里面设置的不同将导致返回不同的状态码:   1. 404指向的是一个htm 文件,此时页面返回的404状态码,这是正确的。   2. 404指向的是一个URL,例如 /error.asp,如果不在页面里面进行设置,仅仅是返回提示的HTML 代码,将导致页面返回200 状态码,此时的危害在于,当很多页面找不到时,都返回和访问正常页面时返回一样的200状态码,将使搜索引擎认为该链接存在,并以错误页面的内容进行收录,当这样的链接很多时,将导致大量页面重复,使网站排名降低。处理方法:在显示完提示内容后,增加语句: Response.Status=”404 Not Found” ,这样就保证页面返回404状态码。   3. 避免在调用404 页面的时候返回302状态码,容易被搜索引擎认为是重定向作弊。   4. 检测方法,使用HttpWatch 查看返回代码。
全栈程序员站长
2022/09/15
6160
SEO之404页面应该怎么做?
404页面是什么? 404是网页反馈的状态码之一,4开头的状态码是指用户错误,5开头的是服务器错误。而404页面就是当用户提交信息后服务器无法回应或者反馈信息就会出现404页面。主要原因就是用户提交了错误信息,或者原内容没移动或者删除导致。 404页面的主要作用: 404主要是反馈给用户所请求的也面错误或者不存在的同时不是让用户离开而是继续浏览其他页面。目前很多的空间后台都是可以设置404页面,如果设置不了网上的方法还是非常的多的。这里简单说一个方法(Apache服务器404页面建立方法):很简单就是在
企鹅号小编
2018/01/25
1.1K0
如何正确检测或处理网站死链接?
网站死链接一般是指内容死链接,服务器返回状态是正常的,但内容已经变更为不存在、已删除或需要权限等与原内容无关的信息页面。
茹莱神兽
2022/03/10
1.4K0
如何正确检测或处理网站死链接?
什么是404页面?对网站有什么影响?
什么是 404 页面? 什么是 404 页面?404 页面指的是原来可以正常访问的链接,在某些特殊的原因后失效,在访问这个链接的时候,服务器就会返回 404 状态的错误页面。 出现 404 页面对网站有什么影响? 1、降低搜索引擎对网站的评价。 2、不利于用户体验 当你的网站存在大量的 404 的话搜索引擎就会对你的网站会进行一定的扣分从而被搜索引擎认为是个不好的网站。同样当用户访问你的网站,打开的都是 404 页面,也是很不利于用户体验的。因为大部分的用户,在发现这个自己所需要的页面不存在的时候,就会关闭
沈唁
2018/05/24
1.3K0
什么是404错误页面,如何制作和优化?
用户访问网站上不存在的页面时,服务器通常应该返回404错误。如果站长没有在服务器端设置客制化的404页面,用户浏览器显示的将会是一个默认的错误页面。
茹莱神兽
2022/09/08
7540
什么是404错误页面,如何制作和优化?
hexo创建公益404界面
在你的博客根目录下``执行命令 hexo new page 404 然后你的博客blog\source\404\ 目录下会生成新的index.md文件
姓王者
2024/10/31
1640
如何处理WordPress网站404状态死链
如果网站存在大量的404状态码的URL地址(即所谓的死链),这将是对网站SEO优化是一个致命的打击,严重影响网站搜索引擎站点评级,不利于网站页面的搜索引擎收录及排名。
开心分享
2020/08/05
4.9K0
如何处理WordPress网站404状态死链
如何制作404页面
第二步:粘贴刚才复制的代码到编辑器,更改对应的跳转链接,文字,以及页面的标题,404图片路径。查看修改编码方式,如不修改可能出现乱码,命名为404.html。
全栈程序员站长
2022/09/18
1.2K0
如何制作404页面
如何让用html制作404页面,网站404页面怎么做?
首先,你可以简单的做一个html页面,把它命名为:404.html页面;如果不会制作,最简单的办法就是找任何一个比较有名的网站,把它的404页面另存为下来,然后修改上面的文字,以及URL为自己的文字信息,再保存好上传到你网站的根目录就行了。
全栈程序员站长
2022/09/18
2.6K0
Thinkphp框架自定义404页面
404页面我们在浏览网页的过程中都遇到过,简单来说就是搜索的东西服务器无法找到,给你返回一个提示信息,但是真正的404意义又是什么呢?请查看文章:404的真正意义;
申霖
2019/12/27
1.8K0
Thinkphp框架自定义404页面
改善用户体验的404页面最佳实践
无论一个网站设计得多好,时不时地,它可能会出现错误、漏洞和故障。此外,用户可能会输入错误的URL,或访问一个破碎的网站链接或页面,从而产生错误。当这种情况发生时,网站访问者会突然面对标准的 "404 "错误信息。404-错误信息表明,网站上的一个页面未能返回结果,不再存在。在搜索结果失败后遇到404错误网站信息的用户通常会被重定向到其他的网站页面。一个经过深思熟虑设计的定制404错误网站信息,其创意和轻松的细节可以区分出沮丧或有趣的用户。自定义404错误网站信息的原创和俏皮的设计细节会影响网站访问者的整体用户体验(UX)。自定义404错误信息的独特风格和信息传递也可能影响用户在未来返回您的网站的意愿。
奔跑的小鹿
2022/07/18
1.2K0
改善用户体验的404页面最佳实践
404页面对网站的好处有哪些?
越来越多的企业需要建设网站来推广公司的业务,网站成为每个企业的标配。为了建设好网站,我们不能忽视网站建设中任何的一个细节,不能仅仅关注网站设计,其他的我们也要关注,不能忽视。比如,404页面就是我们常常忽视的一个,404页面在网站中不常见,但不能忽视,对网站是有很大的好处的。
华专网络
2025/03/20
650
宝塔BT面板下关闭默认404页面方法
在使用宝塔面板搭建typecho后,会发现typecho主题自带404页面无法使用,始终会跳转到宝塔BT默认的404页面,这个页面上没有任何的内容,无论对用户还是搜索引擎来说都是非常不友好,所以我们最好将其禁用掉,使用自己在typecho中设置的404页面,下面说下如何关闭
Xcnte
2021/12/14
1.1K0
宝塔BT面板下关闭默认404页面方法
404页面对SEO的影响
当你打开某一个网站的内页页面不存在,提示页面不存在或者连接错误,该页面上可以访问到网站的其他页面,这样的页面称之为404页面。
OECOM
2020/07/02
6690
采集软件-免费采集软件下载
怎么用免费采集软件让网站快速收录以及关键词排名,网站优化效果主要取决于各个页面权重高低,各个页面权重汇集在一起,网站优化效果才会更加明显,那么各个页面具体权重取决于哪些因素呢?接下来为大家分享一下自己的经验。
用户6632349
2022/03/03
1.4K0
采集软件-免费采集软件下载
推荐阅读
相关推荐
怎么自定义服务器的404,如何自定义404页面
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文