Feign 拦截器:请求增强与上下文传递的利器
Feign 拦截器(RequestInterceptor)是 Feign 提供的请求增强机制,可在 HTTP 请求发送前对其进行统一处理(如添加请求头、修改参数、日志记录等),常用于传递上下文信息(如用户认证令牌、客户端 IP)、统一配置请求头等场景。
Feign 拦截器的核心作用
Feign 拦截器通过实现RequestInterceptor接口的apply方法,在请求构建完成后、发送前对RequestTemplate进行修改,实现以下核心功能:
- 传递上下文信息:如将当前用户的 Token、用户 ID 从上游服务传递到下游服务。
- 统一添加请求头:如添加
Content-Type、Accept、X-Request-Id等通用头信息。
- 请求日志记录:记录请求 URL、参数等信息,便于调试和审计。
- 参数加密 / 签名:对敏感参数进行加密,或为请求添加签名验证。
Feign 拦截器的实现与配置
1. 自定义拦截器实现
通过实现RequestInterceptor接口,重写apply方法编写拦截逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration;
public class FeignContextInterceptor implements RequestInterceptor {
@Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); if (!"Host".equalsIgnoreCase(headerName)) { String headerValue = request.getHeader(headerName); template.header(headerName, headerValue); } } } String clientIp = getClientIp(request); template.header("X-Client-IP", clientIp); } }
private String getClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } }
|
2. 注册拦截器
拦截器需要注册到 Feign 的配置中才能生效,支持全局注册和局部注册两种方式:
(1)全局注册(对所有 Feign 客户端生效)
通过@Configuration类注册拦截器,使其对所有 Feign 客户端生效:
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FeignGlobalConfig { @Bean public RequestInterceptor feignContextInterceptor() { return new FeignContextInterceptor(); } }
|
(2)局部注册(仅对特定 Feign 客户端生效)
在@FeignClient的configuration属性指定的配置类中注册拦截器,仅对当前客户端生效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class FeignLocalConfig { @Bean public RequestInterceptor feignLocalInterceptor() { return template -> { template.header("X-Service-Name", "user-service"); }; } }
@FeignClient( name = "USER-SERVICE", configuration = FeignLocalConfig.class ) public interface UserClient { ... }
|
常见使用场景与最佳实践
1. 传递认证令牌(Token)
在分布式系统中,用户登录后的 Token 需要在服务间传递,通过拦截器可自动将 Token 添加到 Feign 请求头:
1 2 3 4 5 6 7 8 9 10 11
| @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); String token = request.getHeader("Authorization"); if (token != null) { template.header("Authorization", token); } } }
|
2. 统一添加日志追踪 ID
为便于分布式追踪,为每个请求添加唯一的X-Trace-Id,若上游已存在则复用,否则生成新 ID:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String traceId; if (attributes != null) { HttpServletRequest request = attributes.getRequest(); traceId = request.getHeader("X-Trace-Id"); } if (traceId == null || traceId.isEmpty()) { traceId = UUID.randomUUID().toString(); } template.header("X-Trace-Id", traceId); }
|
3. 记录 Feign 请求日志
在拦截器中记录请求 URL、方法、参数等信息,辅助调试:
1 2 3 4 5 6 7
| @Override public void apply(RequestTemplate template) { System.out.println("Feign请求URL:" + template.url()); System.out.println("请求方法:" + template.method()); System.out.println("请求参数:" + template.queryLine()); }
|
注意事项
- 线程安全问题:
RequestInterceptor的apply方法可能被多线程调用,需确保拦截器无状态(如不使用成员变量存储请求相关信息)。
- RequestContextHolder 的局限性:
RequestContextHolder依赖 ThreadLocal,仅在 Web 环境(如 Tomcat 线程)中有效。若 Feign 调用发生在异步线程(如@Async方法),需手动传递上下文信息。
- 避免循环依赖:拦截器中若注入 Feign 客户端,可能导致循环依赖(Feign 客户端依赖拦截器,拦截器又依赖 Feign 客户端),需谨慎设计。
- 请求头冲突:若拦截器添加的头信息与 Feign 接口方法中定义的头信息冲突,后者会覆盖前者(方法级配置优先级更高)。
总结
Feign 拦截器是实现请求统一处理的核心组件,通过它可便捷地传递上下文、添加通用头信息、记录日志等,显著提升微服务间通信的灵活性和可维护性。在实际使用中,需根据业务场景选择全局或局部注册方式,并注意线程安全和上下文传递的局限性,确保拦截器逻辑可靠高效
v1.3.10