場景
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