Feign拦截机制

  1. 核心原理:动态代理
    Feign 使用 JDK 动态代理 或 CGLIB 为接口创建代理对象。
// 你定义的 Feign 接口
@FeignClient(name = "business-service", url = "http://localhost:5372")
public interface PayVipFeignClient {
    @GetMapping("/pay/vip/day")
    Result<Boolean> isVip(@RequestParam("userId") Long userId);
}

// Spring 实际创建的是代理对象
PayVipFeignClient proxy = (PayVipFeignClient) Proxy.newProxyInstance(
    classLoader,
    new Class[]{PayVipFeignClient.class},
    new FeignInvocationHandler()  // 调用处理器
);
  1. 完整调用链路
你的代码:
payVipFeignClient.isVip(109L, 5)
    ↓
【第1层】Feign 动态代理拦截
ReflectiveFeign.FeignInvocationHandler.invoke()
    ↓
【第2层】构建 HTTP 请求
SynchronousMethodHandler.invoke()
    ↓
【第3层】执行 RequestInterceptor 拦截器链
for (RequestInterceptor interceptor : interceptors) {
    interceptor.apply(template);  // ← 这里执行 FeignUserInfoInterceptor
}
    ↓
【第4层】编码请求参数
将 @RequestParam 转换为 URL 查询参数
    ↓
【第5层】发送 HTTP 请求
Client.execute(request)
    ↓
HTTP POST http://localhost:5372/pay/vip/day?userId=109&nameId=5
    ↓
business-service 接收请求

  1. RequestInterceptor 的执行时机
    feign源码逻辑
// SynchronousMethodHandler.java (Feign 内部类)
public Object invoke(Object[] argv) throws Throwable {
    // 1. 创建请求模板
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    
    // 2. 【关键】执行所有拦截器
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);  // ← FeignUserInfoInterceptor 在这里执行
    }
    
    // 3. 构建最终请求
    Request request = template.request();
    
    // 4. 发送 HTTP 请求
    Response response = client.execute(request, options);
    
    // 5. 解码响应
    return decode(response);
}
  1. FeignUserInfoInterceptor 的工作流程
@Component
public class FeignUserInfoInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        // 步骤1:从 ThreadLocal 获取当前用户信息
        UserInfoDTO user = UserContext.get();
        
        if (user == null) {
            return;  // 没有用户信息,不添加请求头
        }
        
        // 步骤2:编码用户信息
        String encoded = UserInfoCodeUtil.encode(user);
        // 例如: "eyJ1c2VySWQiOjEwOSwibmFtZUlkIjo1LCJsb2dpblR5cGUiOjJ9"
        
        // 步骤3:添加到 HTTP 请求头
        template.header(ApiConstants.HEADER_USER, encoded);
        // 最终请求头: X-User-Info: eyJ1c2VySWQiOjEwOSwibmFtZUlkIjo1LCJsb2dpblR5cGUiOjJ9
    }
}
  1. 实际工作流程
1. auth-service 接收网关请求
   → UserInfoServletFilter 解码 X-User-Info
   → UserContext.set(userInfo)  // 存入 ThreadLocal
   
2. 执行业务逻辑
   → RoleService.getFriendList(109L)
   
3. 调用 Feign 接口
   → payVipFeignClient.isVip(109L, 5)
   
4. Feign 动态代理拦截
   → ReflectiveFeign.FeignInvocationHandler.invoke()
   
5. 执行 RequestInterceptor 链
   → FeignUserInfoInterceptor.apply(template)
      → UserInfoDTO user = UserContext.get();  // 从 ThreadLocal 获取
      → String encoded = encode(user);
      → template.header("X-User-Info", encoded);
   
6. 发送 HTTP 请求
   GET http://localhost:5372/pay/vip/day?userId=109&nameId=5
   Headers: {
     "X-User-Info": "eyJ1c2VySWQiOjEwOSwibmFtZUlkIjo1LCJsb2dpblR5cGUiOjJ9"
   }
   
7. business-service 接收
   → UserInfoServletFilter 拦截
   → 解码 X-User-Info
   → UserContext.set(decodedUserInfo)
   
8. business-service 业务逻辑
   → PayVipController.isVip() 可以获取用户信息

  1. 多个拦截器执行顺序
    如果有多个 RequestInterceptor:
    通过@Order控制顺序 或 按照顺序执行
@Component
public class FeignUserInfoInterceptor implements RequestInterceptor {
    public void apply(RequestTemplate template) {
        // 添加用户信息
        template.header("X-User-Info", encoded);
    }
}

@Component
public class FeignTraceInterceptor implements RequestInterceptor {
    public void apply(RequestTemplate template) {
        // 添加链路追踪 ID
        template.header("X-Trace-Id", traceId);
    }
}

@Component
@Order(1)  // 优先级高,先执行
public class FeignUserInfoInterceptor implements RequestInterceptor { ... }

@Component
@Order(2)  // 优先级低,后执行
public class FeignTraceInterceptor implements RequestInterceptor { ... }
  1. ThreadLocal
    为什么能从 ThreadLocal 获取用户信息?
    关键点:
    整个请求处理在同一个线程中完成
    ThreadLocal 保证线程隔离,不同请求互不影响
    Feign 调用是同步的,共享同一个 ThreadLocal
请求线程生命周期:

1. 网关转发请求到 auth-service
   Thread: http-nio-8081-exec-1
   ↓
2. UserInfoServletFilter 拦截
   → 解码 X-User-Info
   → UserContext.set(userInfo)  // 存入 ThreadLocal
   ↓
3. Controller 执行业务逻辑
   → 同一线程: http-nio-8081-exec-1
   → UserContext.get() 可以获取
   ↓
4. Feign 调用其他服务
   → 同一线程: http-nio-8081-exec-1
   → FeignUserInfoInterceptor.apply()
   → UserContext.get() 仍然可以获取 ✅
   ↓
5. 请求结束
   → UserInfoServletFilter 清理 ThreadLocal
   → UserContext.clear()

  1. 对比:AOP 拦截 vs Feign 拦截
    image
  2. 总结技术使用
    FeignUserInfoInterceptor 的作用就是在 HTTP 请求发送前,自动从 ThreadLocal 获取用户信息并添加到请求头中,实现微服务间的用户上下文透传。
定义 Feign 接口
    ↓
Spring 创建动态代理
    ↓
调用接口方法
    ↓
FeignInvocationHandler 拦截
    ↓
执行 RequestInterceptor 链(包括 FeignUserInfoInterceptor)
    ↓
修改 RequestTemplate(添加请求头等)
    ↓
发送 HTTP 请求

posted @ 2026-06-10 10:15  4加1等于9  阅读(11)  评论(0)    收藏  举报