Skip to content

循环引用导致FeignClient的RequestInterceptor拦截器失效

最近使用Spring-Cloud的OpenFeign时发现拦截器(用来给请求头header添加token请求头)失效的问题

我使用OpenFeign的步骤

  1. 定义api接口

  2. 创建调用外部服务的FeignClient并继承api接口

    • 如果需要提供给内部服务使用,在创建一个Controller实现api接口
    • 其它服务在创建调用的Controller并继承api接口
  3. 配置拦截器

问题代码复现

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文章中描述优化实践代码

/src/technology/dateblog/2025/03/20250331-%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E5%AF%BC%E8%87%B4FeignClient%E7%9A%84RequestInterceptor%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%E5%99%A8%E5%A4%B1%E6%95%88.html