Feign拦截器
Feign 拦截器:请求增强与上下文传递的利器
Feign 拦截器(RequestInterceptor)是 Feign 提供的请求增强机制,可在 HTTP 请求发送前对其进行统一处理(如添加请求头、修改参数、日志记录等),常用于传递上下文信息(如用户认证令牌、客户端 IP)、统一配置请求头等场景。
Feign 拦截器的核心作用
Feign 拦截器通过实现RequestInterceptor接口的apply方法,在请求构建完成后、发送前对RequestTemplate进行修改,实现以下核心功能:
- 传递上下文信息:如将当前用户的 Token、用户 ID 从上游服务传递到下游服务。
- 统一添加请求头:如添加
Content-Type、Accept、X-Request-Id等通用头信息。 - 请求日志记录:记录请求 URL、参数等信息,便于调试和审计。
- 参数加密 / 签名:对敏感参数进行加密,或为请求添加签名验证。
Feign 拦截器的实现与配置
1. 自定义拦截器实现
通过实现RequestInterceptor接口,重写apply方法编写拦截逻辑:
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;
/**
* Feign拦截器:传递请求头和上下文信息
*/
public class FeignContextInterceptor implements RequestInterceptor {
public void apply(RequestTemplate template) {
// 从当前请求上下文获取HttpServletRequest(仅适用于Web环境)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 1. 传递所有上游请求头(如Token、用户信息)
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
// 跳过Feign自带的头信息(如Host),避免冲突
if (!"Host".equalsIgnoreCase(headerName)) {
String headerValue = request.getHeader(headerName);
template.header(headerName, headerValue);
}
}
}
// 2. 添加自定义请求头(如客户端IP)
String clientIp = getClientIp(request);
template.header("X-Client-IP", clientIp);
}
}
// 获取客户端真实IP
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 客户端生效:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class FeignGlobalConfig {
// 注册全局Feign拦截器
public RequestInterceptor feignContextInterceptor() {
return new FeignContextInterceptor();
}
}
(2)局部注册(仅对特定 Feign 客户端生效)
在@FeignClient的configuration属性指定的配置类中注册拦截器,仅对当前客户端生效:
// 局部配置类(不被@ComponentScan扫描)
public class FeignLocalConfig {
public RequestInterceptor feignLocalInterceptor() {
return template -> {
template.header("X-Service-Name", "user-service"); // 仅为当前服务添加的头信息
};
}
}
// Feign客户端引用局部配置
public interface UserClient { ... }
常见使用场景与最佳实践
1. 传递认证令牌(Token)
在分布式系统中,用户登录后的 Token 需要在服务间传递,通过拦截器可自动将 Token 添加到 Feign 请求头:
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); // 传递Token
}
}
}
2. 统一添加日志追踪 ID
为便于分布式追踪,为每个请求添加唯一的X-Trace-Id,若上游已存在则复用,否则生成新 ID:
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");
}
// 若上游无TraceId,生成新的UUID
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString();
}
template.header("X-Trace-Id", traceId);
}
3. 记录 Feign 请求日志
在拦截器中记录请求 URL、方法、参数等信息,辅助调试:
public void apply(RequestTemplate template) {
// 记录请求信息(生产环境建议使用日志框架,如SLF4J)
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 拦截器是实现请求统一处理的核心组件,通过它可便捷地传递上下文、添加通用头信息、记录日志等,显著提升微服务间通信的灵活性和可维护性。在实际使用中,需根据业务场景选择全局或局部注册方式,并注意线程安全和上下文传递的局限性,确保拦截器逻辑可靠高效