FeignClient引起ApplicationListener.onApplicationEvent()多次執行

場景

Spring cloud 或者 Spring boot項目中,使用FeignClient 實現客戶端調用。項目中有通過ApplicationListener初始化的方法。

@Component
@Slf4j
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("--------- 執行 監聽器,event:{}",event.getApplicationContext().getDisplayName());
    }
}

問題現象

  • 項目啓動時,ApplicationListener.onApplicationEvent() 執行兩次。
  • FeignClient第一次調用超時,也會觸發執行ApplicationListener.onApplicationEvent() 。
  • 調用服務異常時,觸發ApplicationListener.onApplicationEvent()執行一次。

通過debug 代碼,發現啓動時執行的兩次,第一次的事件是 FeignContext :

 第二次執行:

事件是spring boot啓動加載事件

 服務接口調用異常,觸發fallback 熔斷器時,也會執行:

 

github上有人提交issues:https://github.com/spring-cloud/spring-cloud-openfeign/issues/119

但到目前還沒有解決該bug.

可是換做其他啓動init方式,spring boot 還可以使用以下方式初始化,避免與Feign 衝突。

方法一:

@Component
@Slf4j
public class MyPostConstruct {

    @Value("${spring.application.name}")
    private String name;

    @PostConstruct
    private void init() {
        log.info("appName:{}",name);
    }
}

注意:使用方法一時,不能同時使用@ConfigurationProperties 註解,否則也會導致加載兩次。

方法二:

@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("MyApplicationRunner run...");
    }
}

方法三:

@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner {

    @Value("${spring.application.name}")
    private String name;

    @Override
    public void run(String... args) throws Exception {
        log.info("MyStartRunner run...,appName:{}",name);
    }
}

以上三種方式,都不會出現啓動加載兩次的現象。

如果必須要使用ApplicationListener方式,可以使用以下方式,避免重複加載:

@Component
@Slf4j
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("--------- 執行 監聽器,event:{}",event.getApplicationContext().getDisplayName());
        
        String displayName = event.getApplicationContext().getDisplayName();
        if(displayName.contains("FeignContext") || displayName.contains("SpringClientFactory")) {
            return;
        }
        //todo semo code
    }

}

關於第一次訪問超時的問題,目前沒有太好的解決方式,只能是配置較長的超時時間。

配置如下:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000
feign:
  hystrix:
    enabled: true
  ribbon:
    warmup: true

ribbon:
  ConnectTimeout: 20000
  ReadTimeout: 20000

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章