跳到主要内容

请求拦截器

请求拦截器是 OpenFeign 中非常强大的功能,它允许你在请求发送前和响应接收后进行统一处理。本章将深入讲解拦截器的使用场景和实现方式。

拦截器概述

OpenFeign 提供了两种拦截器接口:

  • RequestInterceptor:在请求发送前执行,用于修改请求
  • ResponseInterceptor:在响应接收后执行,用于处理响应

拦截器的典型应用场景:

  • 添加认证信息(Token、签名等)
  • 添加公共请求头
  • 日志记录
  • 请求/响应加密解密
  • 链路追踪

RequestInterceptor

基本用法

RequestInterceptor 接口只有一个方法:

public interface RequestInterceptor {
void apply(RequestTemplate template);
}

实现一个简单的拦截器:

public class AuthInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
// 在请求头中添加认证信息
template.header("Authorization", "Bearer " + getToken());
}

private String getToken() {
// 从上下文或缓存中获取 Token
return "your-token-here";
}
}

注册拦截器

方式一:配置类注册

public class FeignConfig {

@Bean
public RequestInterceptor authInterceptor() {
return new AuthInterceptor();
}
}

方式二:YAML 配置

spring:
cloud:
openfeign:
client:
config:
user-service:
requestInterceptors:
- com.example.interceptors.AuthInterceptor

常见拦截器实现

认证拦截器

public class BearerAuthInterceptor implements RequestInterceptor {

private final TokenService tokenService;

public BearerAuthInterceptor(TokenService tokenService) {
this.tokenService = tokenService;
}

@Override
public void apply(RequestTemplate template) {
String token = tokenService.getCurrentToken();
if (token != null) {
template.header("Authorization", "Bearer " + token);
}
}
}

请求 ID 拦截器

public class RequestIdInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
String requestId = UUID.randomUUID().toString();
template.header("X-Request-Id", requestId);

// 如果有链路追踪上下文,使用 trace ID
MDC.put("requestId", requestId);
}
}

多租户拦截器

public class TenantInterceptor implements RequestInterceptor {

private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>();

public static void setTenantId(String tenantId) {
TENANT_ID.set(tenantId);
}

public static void clearTenantId() {
TENANT_ID.remove();
}

@Override
public void apply(RequestTemplate template) {
String tenantId = TENANT_ID.get();
if (tenantId != null) {
template.header("X-Tenant-Id", tenantId);
}
}
}

签名拦截器

public class SignatureInterceptor implements RequestInterceptor {

private final String secretKey;

public SignatureInterceptor(String secretKey) {
this.secretKey = secretKey;
}

@Override
public void apply(RequestTemplate template) {
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = UUID.randomUUID().toString();

// 计算签名
String signature = calculateSignature(template, timestamp, nonce);

template.header("X-Timestamp", timestamp);
template.header("X-Nonce", nonce);
template.header("X-Signature", signature);
}

private String calculateSignature(RequestTemplate template, String timestamp, String nonce) {
String method = template.method();
String path = template.path();
String body = template.body() != null ? new String(template.body()) : "";

String data = method + path + body + timestamp + nonce;
return HmacUtils.hmacSha256Hex(secretKey, data);
}
}

日志拦截器

public class LoggingInterceptor implements RequestInterceptor {

private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);

@Override
public void apply(RequestTemplate template) {
log.info("Feign Request: {} {}", template.method(), template.url());
log.debug("Request Headers: {}", template.headers());

if (template.body() != null) {
log.debug("Request Body: {}", new String(template.body()));
}
}
}

拦截器执行顺序

当有多个拦截器时,可以通过 @Order 注解控制执行顺序:

@Bean
@Order(1)
public RequestInterceptor requestIdInterceptor() {
return new RequestIdInterceptor();
}

@Bean
@Order(2)
public RequestInterceptor authInterceptor() {
return new BearerAuthInterceptor(tokenService);
}

@Bean
@Order(3)
public RequestInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}

数字越小,优先级越高,越先执行。

ResponseInterceptor

基本用法

ResponseInterceptor 用于处理响应:

public interface ResponseInterceptor {
Object intercept(InvocationContext invocationContext, Chain chain) throws IOException;

interface Chain {
Object next(InvocationContext invocationContext) throws IOException;
}
}

实现响应拦截器:

public class ResponseLoggingInterceptor implements ResponseInterceptor {

private static final Logger log = LoggerFactory.getLogger(ResponseLoggingInterceptor.class);

@Override
public Object intercept(InvocationContext context, Chain chain) throws IOException {
Object result = chain.next(context);

// 记录响应信息
log.info("Response received for: {}", context.request().url());

return result;
}
}

响应处理拦截器

public class ResponseHandlerInterceptor implements ResponseInterceptor {

@Override
public Object intercept(InvocationContext context, Chain chain) throws IOException {
try {
Object result = chain.next(context);
return result;
} catch (FeignException e) {
// 统一处理 Feign 异常
throw translateException(e);
}
}

private RuntimeException translateException(FeignException e) {
if (e instanceof FeignException.Unauthorized) {
return new AuthenticationException("认证失败");
}
if (e instanceof FeignException.NotFound) {
return new ResourceNotFoundException("资源不存在");
}
return new ServiceException("服务调用失败: " + e.getMessage());
}
}

BasicAuthRequestInterceptor

OpenFeign 内置了 Basic 认证拦截器:

@Bean
public RequestInterceptor basicAuthInterceptor() {
return new BasicAuthRequestInterceptor("username", "password");
}

动态拦截器

根据条件动态决定是否应用拦截逻辑:

public class ConditionalInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
// 只对特定路径添加认证
if (template.path().startsWith("/api/admin")) {
template.header("Authorization", "Bearer admin-token");
}

// 根据请求方法处理
if ("POST".equals(template.method()) || "PUT".equals(template.method())) {
template.header("Content-Type", "application/json");
}
}
}

拦截器与 Spring 上下文

拦截器可以注入 Spring Bean:

@Component
public class AuthInterceptor implements RequestInterceptor {

private final TokenService tokenService;
private final UserService userService;

public AuthInterceptor(TokenService tokenService, UserService userService) {
this.tokenService = tokenService;
this.userService = userService;
}

@Override
public void apply(RequestTemplate template) {
User currentUser = userService.getCurrentUser();
String token = tokenService.getToken(currentUser.getId());

template.header("Authorization", "Bearer " + token);
template.header("X-User-Id", currentUser.getId().toString());
}
}

拦截器最佳实践

1. 职责单一

每个拦截器只负责一个功能:

// 好的做法:职责单一
public class AuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 只负责认证
}
}

public class LoggingInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 只负责日志
}
}

// 不好的做法:职责混乱
public class EverythingInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 认证、日志、签名都放在一起
}
}

2. 异常处理

拦截器中应该妥善处理异常:

public class SafeAuthInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate template) {
try {
String token = getToken();
if (token != null) {
template.header("Authorization", "Bearer " + token);
}
} catch (Exception e) {
log.warn("Failed to add auth header", e);
// 不抛出异常,让请求继续
}
}
}

3. 性能考虑

避免在拦截器中执行耗时操作:

public class CachedTokenInterceptor implements RequestInterceptor {

private final TokenService tokenService;
private volatile String cachedToken;
private volatile long tokenExpireTime;

public CachedTokenInterceptor(TokenService tokenService) {
this.tokenService = tokenService;
}

@Override
public void apply(RequestTemplate template) {
// 使用缓存的 Token,避免每次请求都获取
if (cachedToken == null || System.currentTimeMillis() > tokenExpireTime) {
synchronized (this) {
if (cachedToken == null || System.currentTimeMillis() > tokenExpireTime) {
TokenInfo tokenInfo = tokenService.getToken();
cachedToken = tokenInfo.getToken();
tokenExpireTime = tokenInfo.getExpireTime();
}
}
}
template.header("Authorization", "Bearer " + cachedToken);
}
}

4. 线程安全

拦截器可能被多线程并发调用,需要保证线程安全:

public class ThreadSafeInterceptor implements RequestInterceptor {

// 使用 ThreadLocal 存储线程上下文
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

@Override
public void apply(RequestTemplate template) {
String context = CONTEXT.get();
if (context != null) {
template.header("X-Context", context);
}
// 清理 ThreadLocal,避免内存泄漏
CONTEXT.remove();
}
}

小结

本章详细介绍了 OpenFeign 拦截器的使用:

拦截器类型用途执行时机
RequestInterceptor修改请求请求发送前
ResponseInterceptor处理响应响应接收后
BasicAuthRequestInterceptorBasic 认证请求发送前

拦截器是 OpenFeign 最强大的扩展点之一,合理使用可以大大简化代码,提高可维护性。

下一章将学习降级与熔断,掌握如何实现服务容错。