循环引用导致FeignClient的RequestInterceptor拦截器失效
最近使用Spring-Cloud的OpenFeign时发现拦截器(用来给请求头header添加token请求头)失效的问题
我使用OpenFeign的步骤
定义api接口
创建调用外部服务的FeignClient并继承api接口
- 如果需要提供给内部服务使用,在创建一个Controller实现api接口
- 其它服务在创建调用的Controller并继承api接口
配置拦截器
问题代码复现
java
// 1.定义api接口
public interface TestApi {
@GetMapping("/token")
JSONObject token();
@GetMapping("/hello")
JSONObject hello();
}java
// 2.创建调用外部服务的FeignClient并继承api接口
@FeignClient(name = "test", url = "http://localhost:7777")
public interface TestClient extends TestApi {
}java
// 3.配置拦截器
@Configuration
@Slf4j
public class FeignConfig {
@Resource
private TestService testService;
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
// 这里的日志并不会打印
log.info("=============拦截器执行了===========");
template.header("Authorization", testService.getToken());
};
}
}java
@Service
@RequiredArgsConstructor
@Slf4j
public class TestService {
private final TestClient testClient;
public String getToken() {
return "token";
}
// ...
}DANGER
FeignConfig.java
log.info("=============拦截器执行了===========");
这里的日志并不会打印,实际上拦截器并没有正确注册
问题出现在当FeignConfig中注入了TestService时,拦截器没有执行。而没有注入时可能正常。这时候需要分析Spring的依赖注入和Bean的创建顺序,以及可能导致的循环依赖问题。
首先考虑循环依赖的可能性。TestService依赖于TestClient,而TestClient作为Feign客户端,在创建时需要FeignConfig中的拦截器。 而FeignConfig又依赖于TestService。这就形成了一个循环依赖:FeignConfig -> TestService -> TestClient -> FeignConfig的拦截器。Spring在启动时处理循环依赖可能会有问题,导致某些Bean无法正确初始化,从而拦截器没有被正确注册,进而导致拦截器中的代码没有被执行。
解决方案
方案1: 使用延迟加载
java
@Service
@Slf4j
public class TestService {
// 这里@Lazy和FeignConfig中的TestService@Lazy二选一添加
@Lazy
@Resource
private TestClient testClient;
public String getToken() {
return "token";
}
// ...
}java
// 3.配置拦截器
@Configuration
@Slf4j
public class FeignConfig {
// 这里@Lazy和TestService中的TestClient@Lazy二选一添加
@Lazy
@Resource
private TestService testService;
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
// 这里的日志并不会打印
log.info("=============拦截器执行了===========");
template.header("Authorization", testService.getToken());
};
}
}WARNING
优点
- 快速解决问题: 只需添加一个注解即可打破循环依赖链,对代码改动最小。
- 避免重构成本: 适合在现有复杂代码基础上快速修复问题,无需调整整体结构。
缺点
掩盖设计问题:
- 循环依赖通常是代码结构不合理的信号,@Lazy可能只是暂时掩盖问题。
- 潜在运行时风险: 如果TestService在拦截器首次触发时仍未初始化完成,可能导致NullPointerException或其他意外行为。
- 依赖链不透明: 后续开发者可能难以理解延迟加载的依赖关系,增加维护成本。
更优的长期解决方案
重构代码: 消除循环依赖(推荐)
核心思路: 将TestService中依赖TestClient的逻辑剥离,避免FeignConfig直接依赖TestService。
后续在OpenFeign文章中描述优化实践代码