You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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切面但均未生效:

  1. 针对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;
    }
}
  1. 针对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切点无法生效的核心原因:

  1. Feign生成的代理类并非直接实现CustomerClient或CustomersApi的常规类,而是Feign内部的特殊代理实现,普通execution表达式无法精准匹配实际执行的方法。
  2. 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,需做以下调整:

  1. 开启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逻辑不变
}
  1. 修改切面逻辑,适配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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 21:53:32