前言

大家在开发中有没有遇到过因循环依赖导致项目启动失败?在排查循环依赖的过程中有没困难?如何避免写出循环依赖的代码?

我没写过循环依赖的代码,作为稳定性负责人,我排查过多次。

有些逻辑简单的代码,循环依赖很容易排查。但是,我们的业务超级复杂,绝大多数循环依赖,一整天都查不出来。

起初我们遇到一个循环依赖处理一个,作为稳定性负责人,技术能干的事,不会让人做第二次,为此,我写了一段循环依赖巡检代码,把循环依赖扼杀在测试环境。

下面介绍下场景及处理思路,未必最优,欢迎交流。

背景

SpringCloud服务在上线时出现BeanCurrentlyInCreationException异常(服务本地启动无异常,测试环境启动也无异常,上线就偶尔异常)。

1,本地模拟如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'studentA': Bean with name 'studentA' has been injected into other beans [studentC] 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.
 

2,生产场景如下图:

异常排查&模拟

经过生产排查和本地测试,产生此异常的场景如下:

1,如果类方法有@Async注解,则可能出现如上异常

2,如果存在循环依赖,并且类方法上有@Async注解,则必然会出现如上异常。

1,场景演示:

在spring中,基于field属性的循环依赖是可以的:

示例代码:

@Service
@Slf4j
public class StudentA { @Autowired
private StudentB studentB;
public String test(){
return studentB.test();
}
public String test1(){
return "Studenta1";
}
} @Slf4j
@Service
public class StudentB { @Autowired
private StudentC studentC; public String test() {
return "Studentb";
} public String test1() {
return studentC.test1();
}
} @Slf4j
@Service
public class StudentC { @Autowired
private StudentA studentA; public String test() {
return studentA.test();
} public String test1() {
return "Studentc1";
}
} @Autowired
private StudentA studentA ;
@Test
public void testA(){
String v= studentA.test();
log.info("testa={}",v);
}

以上代码输出正常

2,异常演示

如果我们的方法加上了@Async 注解。则抛出异常:

@Service
@Slf4j
public class StudentA { @Autowired
private StudentB studentB;
@Async
public String test(){
return studentB.test();
}
@Async
public String test1(){
return "Studenta1";
}
} @Slf4j
@Service
public class StudentB { @Autowired
private StudentC studentC;
@Async
public String test() {
return "Studentb";
}
@Async
public String test1() {
return studentC.test1();
}
} @Slf4j
@Service
public class StudentC { @Autowired
private StudentA studentA;
@Async
public String test() {
return studentA.test();
}
@Async
public String test1() {
return "Studentc1";
}
} @Autowired
private StudentA studentA ;
@Test
public void testA(){
String v= studentA.test();
log.info("testa={}",v);
}
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'studentA': Bean with name 'studentA' has been injected into other beans [studentC] 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.

3,解决异常

A,B,C三个类中,至少一个类的field 必须加@Lazy

@Service
@Slf4j
public class StudentA { @Autowired
@Lazy
private StudentB studentB;
@Async
public String test(){
return studentB.test();
}
@Async
public String test1(){
return "Studenta1";
}
} @Slf4j
@Service
public class StudentB { @Autowired
@Lazy
private StudentC studentC;
@Async
public String test() {
return "Studentb";
}
@Async
public String test1() {
return studentC.test1();
}
} @Slf4j
@Service
public class StudentC { @Autowired
@Lazy
private StudentA studentA;
@Async
public String test() {
return studentA.test();
}
@Async
public String test1() {
return "Studentc1";
}
}

参考 :https://my.oschina.net/tridays/blog/805111

杜绝循环依赖的解决方案

这里根据我司的服务架构介绍一下如何避免循环依赖,我司用的spring cloud 全家桶,注册中心用的consul,配置中心有config 和apollo。

1,查看bean的依赖

spring-cloud 提供了bean信息查看功能,通过ip:端口+/actuator/beans可查看(如何配置actuator这里不详细讨论),如下图,我们可以看到每个bean依赖的beans
 

2,解析bean及其依赖beans

如上,我们可以查看某个bean的依赖项,由此,我们可以递归查找bean是否存在循环引用。

bean 信息模型

@Data
public class ApplicationBeans { private Map<String, ContextBeans> contexts;
} @Data
public class ContextBeans { private Map<String, BeanDescriptor> beans;
private String parentId;
}

3,代码实现

这里检测项目起名alarm-center,检测类为CheckCyclicDependenceService

@Slf4j
@RefreshScope
@Service
public class CheckCyclicDependenceService {

// 服务发现
@Autowired
private DiscoveryClient discoveryClient;

//默认检测 api,admin两个项目,如果想检测其它项目,可在config配置
@Value("${check-serviceids-value:api,admin}")
private String checkServiceIds;


//入口
public void checkCyclicDependence() {
if (Strings.isNullOrEmpty(checkServiceIds)) {
return;
}
List<String> serviceIds = Arrays.asList(checkServiceIds.split(","));
for (String serviceId : serviceIds) {
long start = System.currentTimeMillis();
RestTemplate restTemplate = new RestTemplate();
//根据服务名去consul找一个服务实例
ServiceInstance instance = discoveryClient.getInstances(serviceId).get(0);
String url = instance.getUri() + "/actuator/beans";
//所有的beans信息
String applicationBeansStr = restTemplate.getForObject(url, String.class);
ApplicationBeans applicationBeans = JSONObject.parseObject(applicationBeansStr, ApplicationBeans.class);
long end = System.currentTimeMillis();
log.info("checkCyclicDependence get applicationBeans end,serviceid={},coust={}", serviceId, (end - start));
Map<String, ContextBeans> contexts = applicationBeans.getContexts();
Map<String, BeanDescriptor> qualifiedBeans = new HashMap<>();
for (Map.Entry<String, ContextBeans> conEntry : contexts.entrySet()) {
if (!conEntry.getKey().startsWith(serviceId)) {
continue;
}
ContextBeans contextBeans = conEntry.getValue();
Map<String, BeanDescriptor> beans = contextBeans.getBeans(); for (Map.Entry<String, BeanDescriptor> entry1 : beans.entrySet()) {
String beanName = entry1.getKey().toLowerCase();
BeanDescriptor beanDescriptor = entry1.getValue();
if (!beanDescriptor.getType().startsWith("com.shuidihuzhu") || beanName.endsWith("aspect") || beanName.endsWith("controller")
|| beanName.endsWith("dao") || beanName.endsWith("datasource")
|| beanName.endsWith("fallback") || beanDescriptor.getDependencies().length == 0) {
continue;
}
qualifiedBeans.put(entry1.getKey(), beanDescriptor);
}
}
StringBuilder sb = new StringBuilder();
cyclicDependence(qualifiedBeans, sb);
if (sb.length() > 0) {
sb.append(System.getProperty("line.separator"));
sb.append("重注代码质量,尽量做到无循环依赖");
sb.append(System.getProperty("line.separator"));
sb.append("所属服务:" + serviceId);
//alarmClient.sendByUser(Lists.newArrayList("zhangzhi"), sb.toString());
alarmClient.sendByGroup("cf-server-alarm", sb.toString());
end = System.currentTimeMillis();
}
log.info("checkCyclicDependence end,serviceid={},coust={}", serviceId, (end - start));
} } public void cyclicDependence(Map<String, BeanDescriptor> beans, StringBuilder sb) { for (Map.Entry<String, BeanDescriptor> bean : beans.entrySet()) {
String beanName = bean.getKey();
check(beans, beanName, beanName, 0, sb);
}
} public void check(Map<String, BeanDescriptor> beans, String beanName, String dependenceName, int depth, StringBuilder sb) {
if (depth == 4) {//这里可以指定深度,层级过多,容易栈溢出
return;
}
depth++;
BeanDescriptor bean = beans.get(dependenceName);//依赖项的依赖项
if (bean != null) {
String[] deps = bean.getDependencies();//依赖项
for (String dep : deps) {
if (dep.equals(beanName)) {
sb.append(System.getProperty("line.separator"));
String str = String.format("%s和%s存在循环依赖;", beanName, dependenceName);
sb.append(str);
log.info(str);
} else {
check(beans, beanName, dep, depth, sb);
}
}
} }
}

效果

我们在测试环境有个job 每隔几分钟巡检,有循环依赖就企业微信报警,这里截取一段日志,如下:

最后

水滴保险商城-架构组招java实习生

要求:
1、具备较强的编程基本功,熟练掌握JAVA编程语言,熟悉常用数据结构与算法。
2、了解常用的开源框架和开源服务(Spring,Netty,MySQL,Redis,Tomcat,Nginx 等)。
3、熟悉网络编程、多线程编程、分布式等优先。
4、阅读过spring家族源码优先,有技术博客优先,熟悉spring-cloud技术栈优先。
5、 做事积极主动,有较强的执行能力和和较好的沟通能力。

Spring Cloud杜绝循环依赖的更多相关文章

  1. Spring源代码解析 ---- 循环依赖

    一.循环引用: 1. 定义: 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比方CircularityA引用CircularityB,CircularityB引用Circularit ...

  2. Spring源码-循环依赖源码解读

    Spring源码-循环依赖源码解读 笔者最近无论是看书还是从网上找资料,都没发现对Spring源码是怎么解决循环依赖这一问题的详解,大家都是解释了Spring解决循环依赖的想法(有的解释也不准确,在& ...

  3. Spring中的循环依赖解决详解

    前言 说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚.本文就试着尽自己所能,对此做出一个较详细的解读.另,需注意一点,下文 ...

  4. Spring 如何解决循环依赖问题?

    在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的. 这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能 ...

  5. Spring如何解决循环依赖问题

    目录 1. 什么是循环依赖? 2. 怎么检测是否存在循环依赖 3. Spring怎么解决循环依赖 本文主要是分析Spring bean的循环依赖,以及Spring的解决方式. 通过这种解决方式,我们可 ...

  6. Spring 如何解决循环依赖的问题

    Spring 如何解决循环依赖的问题 https://blog.csdn.net/qq_36381855/article/details/79752689 Spring IOC 容器源码分析 - 循环 ...

  7. Spring如何解决循环依赖?

    介绍 先说一下什么是循环依赖,Spring在初始化A的时候需要注入B,而初始化B的时候需要注入A,在Spring启动后这2个Bean都要被初始化完成 Spring的循环依赖有两种场景 构造器的循环依赖 ...

  8. 面试必杀技,讲一讲Spring中的循环依赖

    本系列文章: 听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configu ...

  9. spring怎么避免循环依赖

    1.循环依赖 (1)概念 对象依赖分为强依赖和弱依赖: 强依赖指的是一个对象包含了另外一个对象的引用,例如:学生类中包含了课程类,在学生类中存在课程类的引用 创建课程类: @Data public c ...

随机推荐

  1. Spring Boot第七弹,别再问我拦截器如何配置了!!!

    持续原创输出,点击上方蓝字关注我吧 前言 上篇文章讲了Spring Boot的WEB开发基础内容,相信读者朋友们已经有了初步的了解,知道如何写一个接口. 今天这篇文章来介绍一下拦截器在Spring B ...

  2. uni-app引入iconfont字体图标

    1 首先进入你的iconfont项目 很好, 看见圈圈的吗 , 我说蓝色的,记住了,选到这个 ,然后点击下载本地项目, 解压完就是这个了 ,然后把 圈起来的放到你的项目文件里面 ,记得引入的时候路径别 ...

  3. 深入研究RocketMQ消费者是如何获取消息的

    前言 小伙伴们,国庆都过的开心吗?国庆后的第一个工作日是不是很多小伙伴还沉浸在假期的心情中,没有工作状态呢? 那王子今天和大家聊一聊RocketMQ的消费者是如何获取消息的,通过学习知识来找回状态吧. ...

  4. 53.Qt-QPdfWriter绘制PDF,支持表单输出

    之前打印PDF都是通过html形式来实现的,但是这次要做的东西,需要打印界面控件,所以需要使用QPdfWriter. 通过QPdfWriter来获取QPainter对象,就能实现在PDF上来画画啦. ...

  5. sop服务治理

    一,为什么需要服务治理: 我们最先接触的单体架构, 整个系统就只有一个工程, 打包往往是打成了 war 包, 然后部署到单一 tomcat 上面, 这种就是单体架构, 如图: 假如系统按照功能划分了, ...

  6. Springboot项目集成JPush极光推送(Java SDK)

    1.由于项目的需求,需要在Android APP上实现消息推送功能,所以引用了极光推送(官网:https://www.jiguang.cn/, 文档:http://docs.jiguang.cn/) ...

  7. 接口管理平台Yapi

    1.介绍 YApi 是由去哪儿移动架构组推出的一款开源项目,是高效.易用.功能强大的 api 管理平台,旨在为开发.产品.测试人员提供更优雅的接口管理服务. 官网:https://yapi.ymfe. ...

  8. 很多人都搞不清楚C语言和C++的关系!今天我们来一探究竟为大家解惑~

    最近,身边有许多小伙伴已经开始学习编程了,但是呢,学习又会碰到许多的问题,其中作为新手小白提到最多的问题就是编程语言的选择. 每次遇到这种问题,看起来很简单,但是又有很多小伙伴搞不清编程语言之间的关系 ...

  9. 迎难而上,QPS提高22+倍

    简介 记录1次性能提升的经历,它最大的挑战不在于性能提升,而在于时间急,涉及的面广(比如:机房F5的SSL/TLS性能,机房互联网流量费和项目投入产出比等).性能指标:至少支持10K QPS,10ms ...

  10. 理解Go协程与并发(转)

    理解Go协程与并发   协程 Go语言里创建一个协程很简单,使用go关键字就可以让一个普通方法协程化: Copy package main import ( "fmt" " ...