0%

Feign拦截器

Feign 拦截器:请求增强与上下文传递的利器

Feign 拦截器(RequestInterceptor)是 Feign 提供的请求增强机制,可在 HTTP 请求发送前对其进行统一处理(如添加请求头、修改参数、日志记录等),常用于传递上下文信息(如用户认证令牌、客户端 IP)、统一配置请求头等场景。

Feign 拦截器的核心作用

Feign 拦截器通过实现RequestInterceptor接口的apply方法,在请求构建完成后、发送前对RequestTemplate进行修改,实现以下核心功能:

  • 传递上下文信息:如将当前用户的 Token、用户 ID 从上游服务传递到下游服务。
  • 统一添加请求头:如添加Content-TypeAcceptX-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;

/**
* Feign拦截器:传递请求头和上下文信息
*/
public class FeignContextInterceptor implements RequestInterceptor {

@Override
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 客户端生效:

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 {
// 注册全局Feign拦截器
@Bean
public RequestInterceptor feignContextInterceptor() {
return new FeignContextInterceptor();
}
}
(2)局部注册(仅对特定 Feign 客户端生效)

@FeignClientconfiguration属性指定的配置类中注册拦截器,仅对当前客户端生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 局部配置类(不被@ComponentScan扫描)
public class FeignLocalConfig {
@Bean
public RequestInterceptor feignLocalInterceptor() {
return template -> {
template.header("X-Service-Name", "user-service"); // 仅为当前服务添加的头信息
};
}
}

// Feign客户端引用局部配置
@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); // 传递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");
}
// 若上游无TraceId,生成新的UUID
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) {
// 记录请求信息(生产环境建议使用日志框架,如SLF4J)
System.out.println("Feign请求URL:" + template.url());
System.out.println("请求方法:" + template.method());
System.out.println("请求参数:" + template.queryLine());
}

注意事项

  1. 线程安全问题RequestInterceptorapply方法可能被多线程调用,需确保拦截器无状态(如不使用成员变量存储请求相关信息)。
  2. RequestContextHolder 的局限性RequestContextHolder依赖 ThreadLocal,仅在 Web 环境(如 Tomcat 线程)中有效。若 Feign 调用发生在异步线程(如@Async方法),需手动传递上下文信息。
  3. 避免循环依赖:拦截器中若注入 Feign 客户端,可能导致循环依赖(Feign 客户端依赖拦截器,拦截器又依赖 Feign 客户端),需谨慎设计。
  4. 请求头冲突:若拦截器添加的头信息与 Feign 接口方法中定义的头信息冲突,后者会覆盖前者(方法级配置优先级更高)。

总结

Feign 拦截器是实现请求统一处理的核心组件,通过它可便捷地传递上下文、添加通用头信息、记录日志等,显著提升微服务间通信的灵活性和可维护性。在实际使用中,需根据业务场景选择全局或局部注册方式,并注意线程安全和上下文传递的局限性,确保拦截器逻辑可靠高效

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10