忍术之手3D
27.55 MB · 2025-11-09
在前面的文章中,我们已经实现了框架的核心功能:注解系统、动态代理和 HTTP 客户端。现在我们要为框架添加一个重要的特性 —— 拦截器机制。
拦截器是框架扩展性的核心,它允许用户在 HTTP 请求的生命周期中插入自定义逻辑,实现诸如:
一个好的拦截器机制应该具备:
拦截器机制本质上是 AOP 的一种实现,它将横切关注点(如日志、安全、事务等)从业务逻辑中分离出来:
业务逻辑:获取用户信息
横切关注点:
- 认证检查
- 日志记录
- 性能监控
- 异常处理
拦截器的执行采用责任链模式,每个拦截器都有机会处理请求,并决定是否继续传递给下一个拦截器:
请求 → 拦截器1 → 拦截器2 → 拦截器3 → HTTP客户端 → 响应
← ← ← ← ←
拦截器在 HTTP 请求的不同阶段提供钩子方法:
package io.github.nemoob.httpclient;
/**
* 请求拦截器接口
* 定义了HTTP请求生命周期中的拦截点
*/
public interface RequestInterceptor {
/**
* 请求前拦截
* 在HTTP请求发送前调用,可以修改请求参数、添加请求头等
*
* @param context 请求上下文
* @throws Exception 处理异常
*/
void preHandle(RequestContext context) throws Exception;
/**
* 响应后拦截
* 在HTTP响应接收后调用,可以处理响应数据、记录日志等
*
* @param context 请求上下文
* @throws Exception 处理异常
*/
void postHandle(RequestContext context) throws Exception;
/**
* 异常处理
* 在请求过程中发生异常时调用,可以进行异常处理、重试等
*
* @param context 请求上下文
* @param ex 异常对象
* @throws Exception 处理异常
*/
void afterThrowing(RequestContext context, Exception ex) throws Exception;
}
为了简化拦截器的实现,我们提供一个抽象基类:
package io.github.nemoob.httpclient;
/**
* 请求拦截器抽象基类
* 提供默认的空实现,子类只需要重写需要的方法
*/
public abstract class AbstractRequestInterceptor implements RequestInterceptor {
@Override
public void preHandle(RequestContext context) throws Exception {
// 默认空实现
}
@Override
public void postHandle(RequestContext context) throws Exception {
// 默认空实现
}
@Override
public void afterThrowing(RequestContext context, Exception ex) throws Exception {
// 默认空实现
}
}
package io.github.nemoob.httpclient;
import java.util.logging.Logger;
/**
* 日志拦截器
* 记录HTTP请求和响应的详细信息
*/
public class LoggingInterceptor extends AbstractRequestInterceptor {
private static final Logger logger = Logger.getLogger(LoggingInterceptor.class.getName());
private boolean logHeaders = true;
private boolean logBody = true;
private int maxBodyLength = 1024;
public LoggingInterceptor() {}
public LoggingInterceptor(boolean logHeaders, boolean logBody, int maxBodyLength) {
this.logHeaders = logHeaders;
this.logBody = logBody;
this.maxBodyLength = maxBodyLength;
}
@Override
public void preHandle(RequestContext context) throws Exception {
Request request = context.getRequest();
if (request == null) {
return;
}
StringBuilder logMessage = new StringBuilder();
logMessage.append("HTTP Request: ")
.append(request.getMethod())
.append(" ")
.append(request.getUrl());
// 记录请求头
if (logHeaders && request.getHeaders() != null && !request.getHeaders().isEmpty()) {
logMessage.append("nHeaders: ");
request.getHeaders().forEach((key, value) ->
logMessage.append("n ").append(key).append(": ").append(value)
);
}
// 记录请求体
if (logBody && request.getBody() != null) {
String body = request.getBody().toString();
if (body.length() > maxBodyLength) {
body = body.substring(0, maxBodyLength) + "... (truncated)";
}
logMessage.append("nBody: ").append(body);
}
logger.info(logMessage.toString());
}
@Override
public void postHandle(RequestContext context) throws Exception {
Response response = context.getResponse();
if (response == null) {
return;
}
long duration = context.getEndTime() - context.getStartTime();
StringBuilder logMessage = new StringBuilder();
logMessage.append("HTTP Response: ")
.append(response.getStatusCode())
.append(" (")
.append(duration)
.append("ms)");
// 记录响应头
if (logHeaders && response.getHeaders() != null && !response.getHeaders().isEmpty()) {
logMessage.append("nHeaders: ");
response.getHeaders().forEach((key, value) ->
logMessage.append("n ").append(key).append(": ").append(value)
);
}
// 记录响应体
if (logBody) {
String body = response.getBodyAsString();
if (body != null) {
if (body.length() > maxBodyLength) {
body = body.substring(0, maxBodyLength) + "... (truncated)";
}
logMessage.append("nBody: ").append(body);
}
}
logger.info(logMessage.toString());
}
@Override
public void afterThrowing(RequestContext context, Exception ex) throws Exception {
long duration = System.currentTimeMillis() - context.getStartTime();
logger.severe(String.format("HTTP Request failed after %dms: %s %s - %s",
duration,
context.getRequest() != null ? context.getRequest().getMethod() : "UNKNOWN",
context.getRequest() != null ? context.getRequest().getUrl() : "UNKNOWN",
ex.getMessage()
));
}
// Getter and Setter methods
public boolean isLogHeaders() {
return logHeaders;
}
public void setLogHeaders(boolean logHeaders) {
this.logHeaders = logHeaders;
}
public boolean isLogBody() {
return logBody;
}
public void setLogBody(boolean logBody) {
this.logBody = logBody;
}
public int getMaxBodyLength() {
return maxBodyLength;
}
public void setMaxBodyLength(int maxBodyLength) {
this.maxBodyLength = maxBodyLength;
}
}
package io.github.nemoob.httpclient;
/**
* 认证拦截器
* 自动为请求添加认证信息
*/
public class AuthInterceptor extends AbstractRequestInterceptor {
private final String token;
private final String headerName;
private final String tokenPrefix;
public AuthInterceptor(String token) {
this(token, "Authorization", "Bearer ");
}
public AuthInterceptor(String token, String headerName, String tokenPrefix) {
this.token = token;
this.headerName = headerName;
this.tokenPrefix = tokenPrefix;
}
@Override
public void preHandle(RequestContext context) throws Exception {
Request request = context.getRequest();
if (request == null) {
return;
}
// 如果请求中已经有认证头,则不覆盖
if (request.getHeaders().containsKey(headerName)) {
return;
}
// 添加认证头
String authValue = tokenPrefix + token;
request.getHeaders().put(headerName, authValue);
}
}
package io.github.nemoob.httpclient;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 重试拦截器
* 在请求失败时自动重试
*/
public class RetryInterceptor extends AbstractRequestInterceptor {
private final int maxRetries;
private final long retryDelay;
private final Set<Integer> retryableStatusCodes;
private final Set<Class<? extends Exception>> retryableExceptions;
public RetryInterceptor() {
this(3, 1000);
}
public RetryInterceptor(int maxRetries, long retryDelay) {
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
// 默认可重试的状态码
this.retryableStatusCodes = new HashSet<>(Arrays.asList(500, 502, 503, 504, 408, 429));
// 默认可重试的异常类型
this.retryableExceptions = new HashSet<>(Arrays.asList(
java.net.SocketTimeoutException.class,
java.net.ConnectException.class,
java.io.IOException.class
));
}
@Override
public void afterThrowing(RequestContext context, Exception ex) throws Exception {
// 获取当前重试次数
Integer retryCount = (Integer) context.getAttribute("retryCount");
if (retryCount == null) {
retryCount = 0;
}
// 检查是否可以重试
if (retryCount >= maxRetries || !isRetryable(ex)) {
throw ex;
}
// 增加重试次数
context.setAttribute("retryCount", retryCount + 1);
// 等待后重试
try {
Thread.sleep(retryDelay * (retryCount + 1)); // 指数退避
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw ex;
}
// 重新执行请求
try {
HttpClient client = context.getClient();
ResponseHandler<?> handler = createResponseHandler(context.getMethod());
Object result = client.execute(context.getRequest(), handler);
// 设置响应到上下文
if (result instanceof Response) {
context.setResponse((Response) result);
}
} catch (Exception retryEx) {
// 递归调用,继续重试逻辑
afterThrowing(context, retryEx);
}
}
/**
* 判断异常是否可重试
*/
private boolean isRetryable(Exception ex) {
// 检查异常类型
for (Class<? extends Exception> retryableType : retryableExceptions) {
if (retryableType.isInstance(ex)) {
return true;
}
}
// 检查HTTP状态码
if (ex instanceof HttpException) {
HttpException httpEx = (HttpException) ex;
return retryableStatusCodes.contains(httpEx.getStatusCode());
}
return false;
}
/**
* 创建响应处理器(简化实现)
*/
private ResponseHandler<?> createResponseHandler(java.lang.reflect.Method method) {
// 这里应该根据方法返回类型创建合适的处理器
// 为了简化,返回一个通用的处理器
return new JsonResponseHandler<>(Object.class, HttpClientFactory.getObjectMapper());
}
}
package io.github.nemoob.httpclient;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* 性能监控拦截器
* 收集HTTP请求的性能统计信息
*/
public class MetricsInterceptor extends AbstractRequestInterceptor {
// 请求计数器
private final ConcurrentHashMap<String, LongAdder> requestCounts = new ConcurrentHashMap<>();
// 响应时间统计
private final ConcurrentHashMap<String, AtomicLong> totalResponseTimes = new ConcurrentHashMap<>();
// 错误计数器
private final ConcurrentHashMap<String, LongAdder> errorCounts = new ConcurrentHashMap<>();
@Override
public void preHandle(RequestContext context) throws Exception {
// 记录请求开始时间(如果还没有记录)
if (context.getStartTime() == 0) {
context.setAttribute("startTime", System.currentTimeMillis());
}
// 增加请求计数
String methodUrl = getMethodUrl(context);
requestCounts.computeIfAbsent(methodUrl, k -> new LongAdder()).increment();
}
@Override
public void postHandle(RequestContext context) throws Exception {
long endTime = System.currentTimeMillis();
long startTime = (Long) context.getAttribute("startTime");
long duration = endTime - startTime;
// 记录响应时间
String methodUrl = getMethodUrl(context);
totalResponseTimes.computeIfAbsent(methodUrl, k -> new AtomicLong()).addAndGet(duration);
}
@Override
public void afterThrowing(RequestContext context, Exception ex) throws Exception {
// 增加错误计数
String methodUrl = getMethodUrl(context);
errorCounts.computeIfAbsent(methodUrl, k -> new LongAdder()).increment();
}
/**
* 获取方法URL标识
*/
private String getMethodUrl(RequestContext context) {
Request request = context.getRequest();
if (request == null) {
return "UNKNOWN";
}
return request.getMethod() + " " + request.getUrl();
}
/**
* 获取性能统计信息
*/
public MetricsReport getMetrics() {
MetricsReport report = new MetricsReport();
for (String methodUrl : requestCounts.keySet()) {
long requestCount = requestCounts.get(methodUrl).sum();
long totalTime = totalResponseTimes.getOrDefault(methodUrl, new AtomicLong()).get();
long errorCount = errorCounts.getOrDefault(methodUrl, new LongAdder()).sum();
double avgResponseTime = requestCount > 0 ? (double) totalTime / requestCount : 0;
double errorRate = requestCount > 0 ? (double) errorCount / requestCount : 0;
report.addMetric(methodUrl, requestCount, avgResponseTime, errorRate);
}
return report;
}
/**
* 性能报告类
*/
public static class MetricsReport {
private final ConcurrentHashMap<String, Metric> metrics = new ConcurrentHashMap<>();
public void addMetric(String methodUrl, long requestCount, double avgResponseTime, double errorRate) {
metrics.put(methodUrl, new Metric(requestCount, avgResponseTime, errorRate));
}
public Metric getMetric(String methodUrl) {
return metrics.get(methodUrl);
}
public ConcurrentHashMap<String, Metric> getAllMetrics() {
return metrics;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Performance Metrics:n");
metrics.forEach((methodUrl, metric) -> {
sb.append(String.format("%s: requests=%d, avgTime=%.2fms, errorRate=%.2f%%n",
methodUrl, metric.requestCount, metric.avgResponseTime, metric.errorRate * 100));
});
return sb.toString();
}
}
/**
* 单个指标类
*/
public static class Metric {
public final long requestCount;
public final double avgResponseTime;
public final double errorRate;
public Metric(long requestCount, double avgResponseTime, double errorRate) {
this.requestCount = requestCount;
this.avgResponseTime = avgResponseTime;
this.errorRate = errorRate;
}
}
}
// 在HttpClientFactory中添加全局拦截器管理
public class HttpClientFactory {
private static final List<RequestInterceptor> globalInterceptors = new ArrayList<>();
/**
* 添加全局拦截器
*/
public static void addGlobalInterceptor(RequestInterceptor interceptor) {
globalInterceptors.add(interceptor);
}
/**
* 移除全局拦截器
*/
public static void removeGlobalInterceptor(RequestInterceptor interceptor) {
globalInterceptors.remove(interceptor);
}
/**
* 清空全局拦截器
*/
public static void clearGlobalInterceptors() {
globalInterceptors.clear();
}
/**
* 获取全局拦截器列表
*/
public static List<RequestInterceptor> getGlobalInterceptors() {
return new ArrayList<>(globalInterceptors);
}
}
通过 @Interceptor 注解配置:
@HttpClient("https://api.example.com")
@Interceptor({LoggingInterceptor.class, AuthInterceptor.class})
public interface UserService {
@GET("/users")
List<User> getUsers();
}
通过 @MethodInterceptor 注解配置:
@HttpClient("https://api.example.com")
public interface UserService {
@GET("/users")
@MethodInterceptor({MetricsInterceptor.class})
List<User> getUsers();
@POST("/users")
@MethodInterceptor({LoggingInterceptor.class, RetryInterceptor.class})
User createUser(@Body User user);
}
拦截器的执行遵循以下顺序:
public class InterceptorChain {
private final List<RequestInterceptor> interceptors;
public InterceptorChain(List<RequestInterceptor> interceptors) {
this.interceptors = new ArrayList<>(interceptors);
}
/**
* 执行前置拦截器
*/
public void executePreHandle(RequestContext context) throws Exception {
for (RequestInterceptor interceptor : interceptors) {
interceptor.preHandle(context);
}
}
/**
* 执行后置拦截器(逆序)
*/
public void executePostHandle(RequestContext context) throws Exception {
for (int i = interceptors.size() - 1; i >= 0; i--) {
try {
interceptors.get(i).postHandle(context);
} catch (Exception e) {
// 记录日志但不中断其他拦截器的执行
System.err.println("PostHandle interceptor failed: " + e.getMessage());
}
}
}
/**
* 执行异常拦截器
*/
public void executeAfterThrowing(RequestContext context, Exception ex) {
for (RequestInterceptor interceptor : interceptors) {
try {
interceptor.afterThrowing(context, ex);
} catch (Exception e) {
// 记录日志但不中断其他拦截器的执行
System.err.println("AfterThrowing interceptor failed: " + e.getMessage());
}
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建请求上下文
DefaultRequestContext context = createRequestContext(method, args);
// 构建拦截器链
List<RequestInterceptor> allInterceptors = buildInterceptorChain(method);
InterceptorChain interceptorChain = new InterceptorChain(allInterceptors);
try {
// 执行前置拦截器
interceptorChain.executePreHandle(context);
// 如果拦截器已经设置了响应,直接返回
if (context.getResponse() != null) {
return handleResponse(context, method);
}
// 构建并执行HTTP请求
HttpRequest request = buildHttpRequest(method, args);
context.setRequest(request);
Object result = executeHttpRequest(context, method);
// 执行后置拦截器
interceptorChain.executePostHandle(context);
return result;
} catch (Exception e) {
// 执行异常拦截器
interceptorChain.executeAfterThrowing(context, e);
throw e;
}
}
/**
* 构建拦截器链
*/
private List<RequestInterceptor> buildInterceptorChain(Method method) {
List<RequestInterceptor> chain = new ArrayList<>();
// 1. 添加全局拦截器
chain.addAll(HttpClientFactory.getGlobalInterceptors());
// 2. 添加类级别拦截器
Class<?> clientClass = method.getDeclaringClass();
if (clientClass.isAnnotationPresent(Interceptor.class)) {
Interceptor interceptorAnnotation = clientClass.getAnnotation(Interceptor.class);
for (Class<? extends RequestInterceptor> interceptorClass : interceptorAnnotation.value()) {
try {
chain.add(interceptorClass.newInstance());
} catch (Exception e) {
throw new RuntimeException("Failed to create interceptor: " + interceptorClass.getName(), e);
}
}
}
// 3. 添加方法级别拦截器
if (method.isAnnotationPresent(MethodInterceptor.class)) {
MethodInterceptor methodInterceptor = method.getAnnotation(MethodInterceptor.class);
for (Class<? extends RequestInterceptor> interceptorClass : methodInterceptor.value()) {
try {
chain.add(interceptorClass.newInstance());
} catch (Exception e) {
throw new RuntimeException("Failed to create method interceptor: " + interceptorClass.getName(), e);
}
}
}
return chain;
}
// 配置全局拦截器
HttpClientFactory.addGlobalInterceptor(new LoggingInterceptor());
HttpClientFactory.addGlobalInterceptor(new MetricsInterceptor());
// 定义接口
@HttpClient("https://api.example.com")
@Interceptor({AuthInterceptor.class})
public interface UserService {
@GET("/users")
List<User> getUsers();
@POST("/users")
@MethodInterceptor({RetryInterceptor.class})
User createUser(@Body User user);
}
// 使用
UserService userService = HttpClientFactory.create(UserService.class);
List<User> users = userService.getUsers();
/**
* 自定义缓存拦截器
*/
public class CacheInterceptor extends AbstractRequestInterceptor {
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
private final long ttl;
public CacheInterceptor(long ttl) {
this.ttl = ttl;
}
@Override
public void preHandle(RequestContext context) throws Exception {
Request request = context.getRequest();
// 只缓存GET请求
if (request.getMethod() != HttpMethod.GET) {
return;
}
String cacheKey = request.getUrl();
CacheEntry entry = cache.get(cacheKey);
if (entry != null && !entry.isExpired()) {
// 缓存命中,设置响应
context.setResponse(entry.response);
}
}
@Override
public void postHandle(RequestContext context) throws Exception {
Request request = context.getRequest();
Response response = context.getResponse();
// 只缓存成功的GET请求
if (request.getMethod() == HttpMethod.GET &&
response.getStatusCode() >= 200 && response.getStatusCode() < 300) {
String cacheKey = request.getUrl();
cache.put(cacheKey, new CacheEntry(response, System.currentTimeMillis() + ttl));
}
}
private static class CacheEntry {
final Response response;
final long expireTime;
CacheEntry(Response response, long expireTime) {
this.response = response;
this.expireTime = expireTime;
}
boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
}
/**
* 条件拦截器
* 只在满足特定条件时执行
*/
public class ConditionalInterceptor extends AbstractRequestInterceptor {
private final Predicate<RequestContext> condition;
private final RequestInterceptor delegate;
public ConditionalInterceptor(Predicate<RequestContext> condition, RequestInterceptor delegate) {
this.condition = condition;
this.delegate = delegate;
}
@Override
public void preHandle(RequestContext context) throws Exception {
if (condition.test(context)) {
delegate.preHandle(context);
}
}
@Override
public void postHandle(RequestContext context) throws Exception {
if (condition.test(context)) {
delegate.postHandle(context);
}
}
@Override
public void afterThrowing(RequestContext context, Exception ex) throws Exception {
if (condition.test(context)) {
delegate.afterThrowing(context, ex);
}
}
}
// 使用示例
RequestInterceptor conditionalLogger = new ConditionalInterceptor(
context -> context.getRequest().getUrl().contains("/api/v1/"),
new LoggingInterceptor()
);
HttpClientFactory.addGlobalInterceptor(conditionalLogger);
合理安排拦截器的执行顺序:
// 推荐的拦截器顺序
HttpClientFactory.addGlobalInterceptor(new MetricsInterceptor()); // 1. 性能监控
HttpClientFactory.addGlobalInterceptor(new LoggingInterceptor()); // 2. 日志记录
HttpClientFactory.addGlobalInterceptor(new AuthInterceptor(token)); // 3. 认证处理
HttpClientFactory.addGlobalInterceptor(new RetryInterceptor()); // 4. 重试逻辑
public class SafeInterceptorChain {
public void executePreHandle(RequestContext context) throws Exception {
Exception firstException = null;
for (RequestInterceptor interceptor : interceptors) {
try {
interceptor.preHandle(context);
} catch (Exception e) {
if (firstException == null) {
firstException = e;
}
// 记录日志但继续执行其他拦截器
logger.error("Interceptor preHandle failed: " + interceptor.getClass().getName(), e);
}
}
// 如果有异常,抛出第一个异常
if (firstException != null) {
throw firstException;
}
}
}
本文详细介绍了 Atlas HTTP Client 框架的拦截器机制设计与实现。关键要点包括: