AOP Pointcut对Feign Client不生效,如何为202状态码添加重试?
问题
项目中有一个继承自OpenAPI生成的CustomersApi接口的Feign Client:
@FeignClient(name = "customerClient", url = "${spring.microservice.tenant-service-gateway.host}", configuration = FeignConfiguration.class) public interface CustomerClient extends CustomersApi { }
已通过FeignConfiguration配置了针对500响应码的重试逻辑:
@Configuration @EnableAspectJAutoProxy public class FeignConfiguration { @Bean public Retryer httpResponseCodeRetryer( @Value("${feign.max-attempt-count}") int retryCount, @Value("${feign.retry-period-seconds}") int retryPeriod ) { return new Retryer.Default(TimeUnit.SECONDS.toMillis(retryPeriod), TimeUnit.SECONDS.toMillis(3L), retryCount); } @Bean public ErrorDecoder httpCodeErrorDecoder() { return new HttpCodeErrorDecoder(); } static class HttpCodeErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { var exception = feign.FeignException.errorStatus(methodKey, response); int status = response.status(); if (status == HttpStatus.INTERNAL_SERVER_ERROR.value()) { return new RetryableException( response.status(), exception.getMessage(), response.request().httpMethod(), exception, null, response.request()); } return exception; } } }
现在希望为202响应码也添加重试功能,编写了两个AOP切面但均未生效:
- 针对
CustomerClient的切面:
@Aspect public class AfterReturningExample { @Around("execution(* com.project.client.CustomerClient.*(..))") public Object customerClient(ProceedingJoinPoint pjp) throws Throwable { var proceed = pjp.proceed(); //some logic here return proceed; } }
- 针对
CustomersApi的切面:
@Around("execution(* com.project.api.CustomersApi+.*(..))") public Object customerApi(ProceedingJoinPoint pjp) throws Throwable { var proceed = pjp.proceed(); //some logic here return proceed; }
解决方案
为什么AOP切面不生效?
Feign Client底层通过JDK动态代理生成代理类,常规AOP切点无法生效的核心原因:
- Feign生成的代理类并非直接实现
CustomerClient或CustomersApi的常规类,而是Feign内部的特殊代理实现,普通execution表达式无法精准匹配实际执行的方法。 - Spring AOP的代理优先级低于Feign自身代理,导致切面无法织入到Feign的执行流程中。
实现202响应码重试的可行方案
方案一:自定义Feign ResponseInterceptor处理成功响应
Feign提供ResponseInterceptor接口,可在响应返回后介入处理,适合针对202这类成功响应做重试判断:
public class RetryOn202Interceptor implements ResponseInterceptor { private final Retryer retryer; public RetryOn202Interceptor(Retryer retryer) { this.retryer = retryer; } @Override public void aroundDecode(Response response, InterceptorContext context) throws IOException { if (response.status() == HttpStatus.ACCEPTED.value()) { try { // 调用Retryer判断是否允许重试 retryer.continueOrPropagate(new RetryableException( response.status(), "Request accepted, retrying...", response.request().httpMethod(), null, null, response.request() )); // 触发重试,重新执行请求 Response retryResponse = context.requestInterceptorChain().proceed(response.request()); // 替换原响应为重试后的响应 context.setResponse(retryResponse); } catch (RetryableException e) { throw new FeignException(e.status(), e.getMessage(), e.request(), e); } } } }
在FeignConfiguration中注册该拦截器:
@Configuration @EnableAspectJAutoProxy public class FeignConfiguration { // 保留原有Retryer和ErrorDecoder Bean @Bean public ResponseInterceptor retryOn202Interceptor(Retryer retryer) { return new RetryOn202Interceptor(retryer); } }
方案二:调整AOP配置强制织入Feign代理
如果坚持使用AOP,需做以下调整:
- 开启CGLIB代理并配置Feign使用CGLIB:
@FeignClient(name = "customerClient", url = "${spring.microservice.tenant-service-gateway.host}", configuration = FeignConfiguration.class, proxyTargetClass = true) public interface CustomerClient extends CustomersApi { }
同时在FeignConfiguration的@EnableAspectJAutoProxy中添加proxyTargetClass = true:
@Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class FeignConfiguration { // 原有Bean逻辑不变 }
- 修改切面逻辑,适配Feign代理并判断202状态:
@Aspect @Component // 必须将切面注册为Spring Bean public class RetryOn202Aspect { private final Retryer retryer; public RetryOn202Aspect(Retryer retryer) { this.retryer = retryer; } @Around("execution(* com.project.api.CustomersApi.*(..))") public Object handle202Retry(ProceedingJoinPoint pjp) throws Throwable { Object result = pjp.proceed(); // 需根据OpenAPI生成的返回类型调整状态码获取逻辑 // 假设返回类型为ResponseEntity<T> if (result instanceof ResponseEntity<?>) { ResponseEntity<?> response = (ResponseEntity<?>) result; if (response.getStatusCode() == HttpStatus.ACCEPTED) { try { retryer.continueOrPropagate(new RetryableException( HttpStatus.ACCEPTED.value(), "Request accepted, retrying...", HttpMethod.valueOf(pjp.getSignature().getName()), // 适配获取请求方法 null, null, null )); // 重新执行方法触发重试 result = pjp.proceed(); } catch (RetryableException e) { throw new FeignException(e.status(), e.getMessage(), null, e); } } } return result; } }
关键注意事项
- Feign的
Retryer仅在捕获到RetryableException时触发重试,无论用拦截器还是AOP,都需要通过抛出该异常来触发重试逻辑。 - 202属于成功响应,
ErrorDecoder仅处理状态码≥400的错误响应,因此无法通过ErrorDecoder实现202的重试。
内容的提问来源于stack exchange,提问作者Sergey Tsypanov
相关产品推荐
相关产品推荐

