Spring AOP 不拦截 Feign.Client 调用

Spring AOP does not intercept Feign.Client calls

我正在尝试使用 Spring AOP 拦截 Feign.Client 调用并将请求和响应记录到我的 Splunk 服务器。我的项目包中的所有方法都按照我的预期被拦截,但 Feign.Client 没有。

这是我的 AOP class:

@Component
@Aspect
public class MyAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* com.example.demo.*.*(..))")
    public void pointCutDemo(){}

    @Pointcut("execution(* feign.Client+.*(..))")
    public void pointCutFeign(){}

    @Around("pointCutDemo()")
    public void myAroundDemo(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("calling joinpoint "+joinPoint.getSignature().getName());
        joinPoint.proceed();
    }

    @Around("pointCutFeign()")
    public void myAroundFeign(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("calling feign joinpoint "+joinPoint.getSignature().getName());
        joinPoint.proceed();
    }
}

方法 myAroundDemo 被多次调用,如我所料,但 myAroundFeign 从未被调用。

我有一个调用我的界面的简单控制器(Feign API),这是控制器:

@RestController
public class Controller {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ExternalAPI externalAPI;

    @GetMapping
    public String get(){
        logger.info("calling get method");
        logger.info(String.valueOf(externalAPI.listUsers()));
        return "I'm here";
    }
}

这是我的 Feign 界面:

@FeignClient(url = "http://localhost:3000", name = "feign", configuration = FeignConfig.class)
public interface ExternalAPI {
    @GetMapping(value = "/menu")
    String listUsers();
}

Spring AOP 仅适用于 Spring 组件。我的猜测是它不起作用,因为 Feign 不是 Spring 组件,因此超出了 Spring AOP 的范围。如果您需要将方面应用于 non-Spring 类,只需使用完整的 AspectJ。 Spring 手册解释了如何 configure it via LTW (load-time weaving).

@kriegaex 是正确的,我不能在 non-Spring 组件中应用 AOP。但我不想使用纯 AspectJ,所以我使用了另一个解决方案。以下是我解决问题的步骤:

1) 启用 Spring Cloud Ribbon 我得到了由 spring 管理的 class LoadBalancerFeignClient 实现了 feign.Client,所以我在 [= =34=] 并更改了我的 application.yml.

application.yml

myfeign:
  ribbon:
    listOfServers: localhost:3000

pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

2) 在MyAspect class 我截取了 LoadBalancerFeignClient class:

@Pointcut("execution(* org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(..))")
public void pointCutFeign(){}

@Around("pointCutFeign()")
public Object myAroundFeign(ProceedingJoinPoint joinPoint) throws Throwable {
    if (joinPoint.getArgs().length > 0) {
        Request request = (Request) joinPoint.getArgs()[0];
        logger.info("REQUEST >>>>>>>>>>>>");
        logger.info("URL = "+request.url());
        logger.info("METHOD = "+request.httpMethod().name());
        logger.info("BODY = "+request.requestBody().asString());
        logger.info("HEADERS = "+request.headers().toString());
    }

    Response response = (Response) joinPoint.proceed();

    logger.info("RESPONSE <<<<<<<<<<<<<<");
    logger.info("STATUS = "+response.status());
    logger.info("HEADERS = "+response.headers().toString());
    logger.info("BODY = " + IOUtils.toString(response.body().asInputStream(), "UTF-8"));
    return response;
}

现在效果很好,我得到了我需要的所有信息。

我也面临这个问题。但我无法拦截 LoadBalancerFeignClient class。我使用相同的代码进行测试,但它不起作用。enter image description here

当我调试函数时,我发现 (this) 指向 TraceLoadBalanceFeignClient.The LoadBalancerFeignClient 的 subclass.finally 我在使用 sleuth 时发现。假装客户端将由 sleuth 的 feignbuilder.The 切入点创建,将无效

Feign 内置了无需 AOP 即可使用的日志记录。如果您创建一个 feign.Logger 实例并注册它,它将记录请求、响应和 headers。

@Bean
public feign.Logger logger() {
    /* requires feign-slf4j */
    return new Slf4jLogger();
}

Logger 个实例提供以下功能:

  • 请求在发送到客户端之前被记录下来。
  • 收到回复后,无论状态如何,都会记录回复。
  • 如果重试被触发,则会记录。

如果您只需要日志记录,这可能是更好的解决方案。

我有一个方法可以在拦截任何假客户端时获取有用的信息,我希望它能帮助别人。

@Before("@within(org.springframework.cloud.openfeign.FeignClient)")
public void logBeforeService(JoinPoint joinPoint) {
    final MethodMetadata methodMetadata = new SpringMvcContract().parseAndValidateMetadata(
                                   joinPoint.getSignature().getDeclaringType(),
                                   ((MethodSignature) joinPoint.getSignature()).getMethod());

    String requestUrl = methodMetadata.template().url();
    String methodName = methodMetadata.template().method();
    log.info("RequestUrl: {}", requestUrl);
    log.info("HttpMethod: {}", methodName);

}