请求拦截器
请求拦截器是 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 | 处理响应 | 响应接收后 |
BasicAuthRequestInterceptor | Basic 认证 | 请求发送前 |
拦截器是 OpenFeign 最强大的扩展点之一,合理使用可以大大简化代码,提高可维护性。
下一章将学习降级与熔断,掌握如何实现服务容错。