SpringCloud BeanCurrentlyInCreationException 異常解決方案

zzhi.wang發表於2020-11-11

前言

大家在開發中有沒有遇到過因迴圈依賴導致專案啟動失敗?在排查迴圈依賴的過程中有沒困難?如何避免寫出迴圈依賴的程式碼?

我沒寫過迴圈依賴的程式碼,作為穩定性負責人,我排查過多次。

有些邏輯簡單的程式碼,迴圈依賴很容易排查。但是,我們的業務超級複雜,絕大多數迴圈依賴,一整天都查不出來。

起初我們遇到一個迴圈依賴處理一個,作為穩定性負責人,技術能幹的事,不會讓人做第二次,為此,我寫了一段迴圈依賴巡檢程式碼,把迴圈依賴扼殺在測試環境。

 

下面介紹下場景及處理思路,未必最優,歡迎交流。

背景

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依賴的bean
 

  

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、 做事積極主動,有較強的執行能力和和較好的溝通能力。

 

對水滴感興趣的同學可找我內推,java 後端和QA 都可以,我找負責人評估合適的話,推給HR聯絡您。

 

相關文章