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