【问题标题】:Aspect call ending before the method is subscribed在订阅方法之前结束方面调用
【发布时间】:2020-10-04 20:41:45
【问题描述】:

我有一个方法,其中包含响应式代码 (RxJava)。

我有一个 Aspect @around 环绕它。

设置很好,打印如下。

但它发生在甚至订阅该方法之前。

有没有一种方法可以设置它,使得 Aspect 仅在方法被订阅后才启动?

我的方面类

@Aspect
public class AspectClass {

        @Around("@annotation(someLogger) && execution(* *(..))")
        public Object getMockedData(ProceedingJoinPoint pjp, SomeLogger someLogger) throws Throwable {
    
            System.out.println("from aspect start: " + Thread.currentThread().getName());
    
            Object actualResponse = pjp.proceed(methodArguments);
    
            System.out.println("from aspect close: " + Thread.currentThread().getName());
    
            return actualResponse;
    
        }
    }

自定义注解

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

    String name() default "";
}

被注释以使用方面的方法。

@SomeLogger(name = "name")
public Single<Response>  add(Request request) {

    return sample(request)
            .flatMapObservable(resultSet -> Observable.from(resultSet.getRows()))
            .map(row -> Response.builder()
                    .sampleTimestamp(localDateTime(row.getInstant("sample")))
                    .build()
            )
            .first()
            .toSingle()
            .doOnSubscribe(() -> System.out.println("method is subbed: add " + Thread.currentThread().getName()))
            .doOnEach(n -> System.out.println("method ends and doOnEach: add " + Thread.currentThread().getName()));

}

如前所述,方面类中的打印行甚至在订阅之前就被打印出来,这是错误的。

因此这是当前错误的打印顺序。

from aspect start: eventloop-thread-3
from aspect close: eventloop-thread-3
method is subbed: add eventloop-thread-3
method ends and doOnEach: add eventloop-thread-3

我期待以下订单。

method is subbed: add eventloop-thread-3
from aspect start: eventloop-thread-3
method ends and doOnEach: add eventloop-thread-3
from aspect close: eventloop-thread-3

这可能吗?

这是我发现的最接近的问题。 Writing Aspects For Reactive Pipelines

但这个问题是基于 Spring 的。该答案中还有一行:

您确实需要让方面了解异步情况。

听起来这是我需要做的,但我该怎么做呢?感谢您的建议。

--- 建议后更新 ---

请注意,我使用的是 AspectJ 而不是 Spring。

这不起作用,因为订阅前变量proceed为空。

因此我添加了一个空检查。但我们只打算进入这里一次。

因此,proceed.doOnSubscribe() 永远不会发生。

@Before("@annotation(someLogger) && execution(* *(..))")
public void before(JoinPoint jp, SomeLogger someLogger) throws Throwable {

    Object[] args = jp.getArgs();

    Single<?> proceed = ((Single<?>) ((ProceedingJoinPoint) jp).proceed(args));

    if(proceed != null) {
        proceed.doOnSubscribe(() -> System.out.println("doOnSubscribe in before"));
    }

    // this will print before a subscription
    // as expected before which is not what I want. 
    System.out.println("inside before");

}

进一步尝试:

至少在理论上期望这会起作用。但会引发 AJC 编译器错误。

@Around("@annotation(someLogger) && execution(* *(..))")
    public Object around(ProceedingJoinPoint pjp, SomeLogger someLogger) {

        // return pjp.proceed(methodArguments); // compiles fine if no operations on method 

        return pjp.proceed(methodArguments)
        .doOnSubscribe(() -> System.out.println("method is subbed: add " + Thread.currentThread().getName()))
        .doOnEach(n -> System.out.println("method ends and doOnEach: add " + Thread.currentThread().getName()));
}

【问题讨论】:

  • 在您最初的问题中,您使用了@Around 建议。这是 ProceedingJoinPoint jpjp.proceed() 适合的地方。编辑后的第二个方面显示@Before 建议。在那里你必须使用普通的JoinPoint,并且你永远不会调用proceed(),因为除非你在通知中抛出异常,否则目标方法将在之后自动执行。您尝试投射并继续表明您不理解这一点。你真的应该阅读一些文档,而不是通过反复试验来破解。
  • @kriegaex 确实阅读了文件。这就是为什么我之前使用 JoinPoint for @ 的原因。了解您在此处不使用 ProceedingJoinPoint 的观点。上述示例在正常情况下可以正常工作。我一直不明白如何在这里的 before 方法中执行 doOnSubscribe 。这不在文档中,至少我看不到。
  • 然后再次阅读文档。尝试将普通连接点从之前的建议转换为预期将在周围建议中使用的正在进行的连接点是没有意义的。打电话给proceed() 更疯狂。我 100% 确定您从未在任何文档、教程或示例代码中发现过这一点。这是完全错误的。在您担心doOnsubscribe() 之前,先了解如何使用 AspectJ。这就是我的观点。
  • @kriegaex 如果不​​是因为我担心 doOnsubscribe() 试图查看是否有解决办法让它工作,我永远不会在 @Before 中为 ProceedingJoinPoint 烦恼。无论如何感谢您的回复。
  • 我不明白。如果您只想捕获结果并记录一些内容,请使用@AfterReturning,而不是@Before。你想在方法返回之后之后做一些事情,而不是之前。如果要更改返回值和/或在原始方法之后的 + 之前执行某些操作,请使用 @Around。如果您不知道,您将无法阅读任何文档。我真的很想在这里提供帮助。第三次也是最后一次,您的问题与doOnSubscribe() 无关。你需要练习和理解 AOP。

标签: java reactive-programming aop aspectj aspect


【解决方案1】:

有什么问题?

问题不在于方面,而在于您对尝试拦截的代码如何以及何时执行的理解。该切面完全按照您的要求执行:它在执行带注释的方法之前和之后记录一些内容。到目前为止,一切顺利。

你应该怎么做

您要拦截的是您在 add 方法中注册的异步回调,这些回调配置了稍后执行的行为。如果您想这样做,您应该使用@Before@After 建议拦截提供给doOnSubscribedoOnEach 的代码,具体取决于您希望何时打印信息(在您的示例日志中,您似乎更喜欢之后)。

A) 如果你使用 Spring AOP

为了做到这一点,您不能将 lambdas 与 Spring AOP 结合使用,因为 Spring AOP 只能拦截 Spring 组件中的代码。因此,您必须将两个 lambda 提取到类中(本质上它们是,只是匿名的),使这些类成为 Spring bean,然后拦截它们的方法。我希望您知道 lambda 基本上是一个匿名类,它使用单个方法实现了一个接口(不是真正在 JVM 字节码内部,而是为了简单理解)。因此,如果您从 ConsumerObserver 或任何您的回调实现的 RxJava 子类创建单独的 Spring 组件,您可以通过 Spring AOP 拦截它们。但这意味着修改您的代码以适应和促进 AOP 的使用。您的代码也会变得不那么简洁和富有表现力。但你问的问题,我只是回答。

B) 如果你使用 AspectJ

如果你从 Spring AOP 切换到 AspectJ 或者已经在使用 AspectJ,你可以尝试直接定位 lambda,但这也很棘手。我必须写一个很长的答案来解释原因,您可以阅读this answerAspectJ issue #471347 了解更多信息。

如果您将 lambdas 转换为经典的匿名子类,它会变得更容易(像 IntelliJ IDEA 这样的优秀 IDE 应该可以帮助您在您要求时通过两次鼠标单击自动为您执行此操作),然后您可以通过 AspectJ 进行拦截。但同样,您将自己的编程风格迎合 AOP 的使用,因为目前 AspectJ 还没有明确支持 lambda。


问题更改后更新

您在大约 1.5 年前,即我回答后大约 1 周编辑了您的问题,提供了额外的示例代码 sn-ps(但遗憾的是,仍然没有 MCVE。但是,评论或回答问题的人没有收到有关问题或答案编辑的通知。如果您想引起他们的注意,您需要在附加评论中通知他们。我今天偶然再次打开这个问题,正在寻找其他东西。所以即使我希望你已经解决了很久以前的问题,让我提供一些反馈记录,因为其他人可能也会偶然发现这个问题。

@Before("...")
public void before(JoinPoint jp, SomeLogger someLogger) throws Throwable {
    Object[] args = jp.getArgs();
    Single<?> proceed = ((Single<?>) ((ProceedingJoinPoint) jp).proceed(args));
    // ...
}

您正在将@Before 建议与ProceedingJoinPoint 混合,这是一个仅在@Around 建议中使用的JoinPoint 子类,方法是尝试对其进行向下转换和proceed()。这注定要失败。这与尝试将Ellipse 的实例转换为Circle(偏心度为0 的特定椭圆)或Rectangle 转换为Square(具有四个相等边的特定矩形)相同。为了能够使用SquareCircleProceedingJoinPoint,您尝试投射的对象实际上需要是一个这样的实例,但这里不是这种情况。前通知中的连接点对象不是正在进行的连接点。

至于您的第二个示例,它是一个@Around 建议,如果您希望更改原始返回值,这很好。但是这里...

    return pjp.proceed(methodArguments)

您正在尝试处理您之前从未通过pjp.getArgs() 定义的参数。就像第一个问题一样,这个问题甚至不是 AOP 问题,而只是在 Java 中使用了一个未定义的变量。当然,它无法编译。实际上,如果您不更改参数,则无需这样做。只需不带参数调用proceed(),它将使用原始参数。

【讨论】:

  • 谢谢。根据“你应该怎么做”下的建议更新了问题。
  • 不,你没有。在 10 月 7 日,您简单地添加了一句话,说明您使用 AspectJ 而不是 Spring AOP。仅仅一周后,您添加了更多代码,但没有再次通过评论通知我(所以除了偶然之外,我没有注意到任何变化)。将编辑历史与您的评论进行比较清楚地证明了这一点。时隔 1.5 年的今天,我碰巧再次打开了这个问题,并注意到您默默地添加了更多代码。所以我刚刚更新了我的答案,评论了你提供的两个新代码片段。如果您仍然对它们为什么不起作用感兴趣,请随时阅读。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-18
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
相关资源
最近更新 更多