- 核心原理:动态代理
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() // 调用处理器
);
- 完整调用链路
你的代码:
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 接收请求
- 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);
}
- 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. 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() 可以获取用户信息
- 多个拦截器执行顺序
如果有多个 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 { ... }
- 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()
- 对比:AOP 拦截 vs Feign 拦截
![image]()
- 总结技术使用
FeignUserInfoInterceptor 的作用就是在 HTTP 请求发送前,自动从 ThreadLocal 获取用户信息并添加到请求头中,实现微服务间的用户上下文透传。
定义 Feign 接口
↓
Spring 创建动态代理
↓
调用接口方法
↓
FeignInvocationHandler 拦截
↓
执行 RequestInterceptor 链(包括 FeignUserInfoInterceptor)
↓
修改 RequestTemplate(添加请求头等)
↓
发送 HTTP 请求