如何使用 AOP 进行 Feign 调用

问题描述 投票:0回答:2

我对如何在AOP中使用Feign客户端感兴趣。例如:

API:

public interface LoanClient {
    @RequestLine("GET /loans/{loanId}")
    @MeteredRemoteCall("loans")
    Loan getLoan(@Param("loanId") Long loanId);
}

配置:

@Aspect
@Component // Spring Component annotation
public class MetricAspect {

    @Around(value = "@annotation(annotation)", argNames = "joinPoint, annotation")
    public Object meterRemoteCall(ProceedingJoinPoint joinPoint, 
                        MeteredRemoteCall annotation) throws Throwable {
    // do something
  }
}

但我不知道如何“拦截”api方法调用。我哪里做错了?

更新:

我的Spring类注释:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MeteredRemoteCall {

    String serviceName();
}
java aop aspectj spring-aop feign
2个回答
9
投票

您的情况有些复杂,因为您有几个问题:

  • 您使用 Spring AOP,一个基于动态代理(接口的 JDK 代理,类的 CGLIB 代理)的“AOP lite”框架。它仅适用于 Spring beans/组件,但从我看来,你的
    LoanClient
    不是 Spring
    @Component
  • 即使是 Spring 组件,Feign 也会通过反射创建自己的 JDK 动态代理。它们不受 Spring 的控制。可能有一种方法可以通过编程或通过 XML 配置手动将它们连接到 Spring 中。但我无法帮助你,因为我不使用 Spring。
  • Spring AOP 仅支持 AspectJ 切入点的子集。具体来说,它不支持
    call()
    ,只支持
    execution()
    。 IE。它只编织到执行方法的地方,而不是调用它的地方。
  • 但是执行发生在实现接口的方法中,并且接口方法上的注释(例如您的
    @MeteredRemoteCall
    )永远不会被其实现类继承。事实上,方法注释在 Java 中永远不会被继承,只是从类(不是接口!)到各个子类的类级注释。 IE。即使您的注释类具有 @Inherited 元注释,它对
    @Target({ElementType.METHOD})
    也没有帮助,仅适用于
    @Target({ElementType.TYPE})
    更新:
    因为我之前已经多次回答过这个问题,所以我刚刚记录了该问题以及使用 AspectJ 模拟接口和方法的注释继承中的解决方法。
  • 那么你能做什么呢?最好的选择是在 Spring 应用程序中通过 LTW(加载时编织)使用完整的 AspectJ。这使您能够使用
call()

切入点,而不是 Spring AOP 隐式使用的 execution()。如果您在 AspectJ 中的方法上使用

@annotation()
切入点,它将匹配调用和执行,正如我将在一个独立的示例中向您展示的那样(没有 Spring,但效果与 Spring 中带有 LTW 的 AspectJ 相同):

标记注释:

package de.scrum_master.app; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MeteredRemoteCall {}

假冒客户:


此示例客户端以字符串形式获取完整的 StackOverflow 问题页面(HTML 源代码)。 package de.scrum_master.app; import feign.Param; import feign.RequestLine; public interface StackOverflowClient { @RequestLine("GET /questions/{questionId}") @MeteredRemoteCall String getQuestionPage(@Param("questionId") Long questionId); }

驱动程序应用程序:


此应用程序以三种不同的方式使用 Feign 客户端界面以进行演示:

无需Feign,通过匿名子类手动实例化

与 #1 类似,但这次在实现方法中添加了额外的标记注释
  1. 通过 Feign 的规范用法
  2. package de.scrum_master.app; import java.util.regex.Matcher; import java.util.regex.Pattern; import feign.Feign; import feign.codec.StringDecoder; public class Application { public static void main(String[] args) { StackOverflowClient soClient; long questionId = 41856687L; soClient = new StackOverflowClient() { @Override public String getQuestionPage(Long loanId) { return "StackOverflowClient without Feign"; } }; System.out.println(" " + soClient.getQuestionPage(questionId)); soClient = new StackOverflowClient() { @Override @MeteredRemoteCall public String getQuestionPage(Long loanId) { return "StackOverflowClient without Feign + extra annotation"; } }; System.out.println(" " + soClient.getQuestionPage(questionId)); // Create StackOverflowClient via Feign String baseUrl = "http://stackoverflow.com"; soClient = Feign .builder() .decoder(new StringDecoder()) .target(StackOverflowClient.class, baseUrl); Matcher titleMatcher = Pattern .compile("<title>([^<]+)</title>", Pattern.CASE_INSENSITIVE) .matcher(soClient.getQuestionPage(questionId)); titleMatcher.find(); System.out.println(" " + titleMatcher.group(1)); } }
不带方面的控制台日志:


StackOverflowClient without Feign StackOverflowClient without Feign + extra annotation java - How to use AOP with Feign calls - Stack Overflow 如您所见,在情况 #3 中,它只打印这个 StackOverflow 问题的问题标题。 ;-) 我使用正则表达式匹配器来从 HTML 代码中提取它,因为我不想打印完整的网页。

方面:

这基本上是您具有附加连接点日志记录的方面。 package de.scrum_master.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import de.scrum_master.app.MeteredRemoteCall; @Aspect public class MetricAspect { @Around(value = "@annotation(annotation)", argNames = "joinPoint, annotation") public Object meterRemoteCall(ProceedingJoinPoint joinPoint, MeteredRemoteCall annotation) throws Throwable { System.out.println(joinPoint); return joinPoint.proceed(); } }

带有方面的控制台日志:


call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long)) StackOverflowClient without Feign call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long)) execution(String de.scrum_master.app.Application.2.getQuestionPage(Long)) StackOverflowClient without Feign + extra annotation call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long)) java - How to use AOP with Feign calls - Stack Overflow 如您所见,对于这三种情况,以下连接点都会被拦截:

call()

,因为即使手动实例化,实现类也没有接口方法的注释。所以
    execution()
  1. 无法匹配。
    call()
  2. execution()
  3. 都是因为我们手动将标记注释添加到实现类中。
    call()
    ,因为Feign创建的动态代理没有接口方法的注解。所以
  4. execution()
  5. 无法匹配。
    
    
    
    我希望这可以帮助您了解发生了什么以及为什么。
  6. 底线:使用完整的 AspectJ 以使切入点与
call()

连接点匹配。那么你的问题就解决了。

也许为时已晚,但可以以更简单的方式进行。您的代码是正确的,只是存在一个小错误。您应该使用@within 而不是@annotation。我的意思是正确的代码是这样的:


0
投票

© www.soinside.com 2019 - 2024. All rights reserved.