4

I have a method which has reactive code in it (RxJava).

I have an Aspect @around wrapped around it.

The setup is fine, it does the print out as follows.

But it happens before the method is even subscribed to.

Is there a way we could set it up such that the Aspect is going to only kick off after the method gets subscribed to?

My Aspect class

@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;
    
        }
    }

The custom annotation

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

    String name() default "";
}

The method that gets annotated to use aspect.

@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()));

}

As mentioned the print lines in the aspect class gets printed even before subscription which is wrong.

Thus this is the current wrong order for the print.

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

I was expecting the following order.

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

Is this possible?

This is the closest question I found with regards to this. Writing Aspects For Reactive Pipelines

But that question is based off Spring. Also there is a line in that answer:

You do need to make the aspect aware of the asynchronous situation.

Sounds like this is what I need to do, but how can I do it? Thanks for any advice.

--- UPDATE after suggestion ---

To note, am using AspectJ not Spring.

This doesn't work cos variable proceed is null before subscription.

Thus I added a null check. But we are only going to enter here once.

Thus proceed.doOnSubscribe() never happens.

@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");

}

Further attempt:

At least in theory was expecting this to work. But throws AJC Compiler errors.

@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()));
}
Fllappy
  • 371
  • 3
  • 11
  • In your original question you used an `@Around` advice. This is where `ProceedingJoinPoint jp` and `jp.proceed()` fit. The second aspect after your edit shows a `@Before` advice. There you have to use a normal `JoinPoint` and you never call `proceed()` because unless you throw an exception in the advice, the target method will be executed automatically afterwards. Your try to cast and proceed shows that you do not understand that. You really ought to read some documentation and not just hack away by trial & error. – kriegaex Oct 08 '20 at 03:06
  • @kriegaex Did read the documents. Which is why I am using JoinPoint for @ before. Understand your point about not using ProceedingJoinPoint here. The above examples are going to work fine in a normal scenario. I am stuck with not understanding how I could do a doOnSubscribe inside the before method here. This is not covered in the documentation, at least none I could see. – Fllappy Oct 08 '20 at 08:34
  • Then read the documentation again. There is no point in trying to cast a normal joinpoint from a before advice to a proceeding joinpoint expected to be used in an around advice. Calling `proceed()` is even more crazy. I am 100% sure you never found that in any documentation, tutorial or sample code. It is just plain wrong. Before you worry about `doOnsubscribe()`, learn how to use AspectJ. That was my point. – kriegaex Oct 08 '20 at 09:06
  • @kriegaex Would never have bothered with ProceedingJoinPoint in a @ Before if not for my worry with doOnsubscribe() trying to see if there is a work around to get it to work. In any case thanks for responding. – Fllappy Oct 08 '20 at 09:17
  • I don't get it. If you just want to capture the result and log something, use `@AfterReturning`, not `@Before`. You want to do something **after** the method has returned, not **before**. If you want to change the return value and/or do something before + after the original method, use `@Around`. You cannot have read any documentation if you don't know that. I am really trying to help here. For the 3rd and last time, your problem has nothing to do with `doOnSubscribe()`. You need to practice and understand AOP. – kriegaex Oct 08 '20 at 11:36
  • Have you given up on this? No more feedback? It is still on my follow-up list. – kriegaex Oct 13 '20 at 02:49
  • @kriegaex "For the 3rd and last time". Wasn't under the impression I was going to get any further feedback after that. You've mentioned more than once now that I need to start reading up aspect now. Fair point, am no expert. But my issue is specifically on how to use aspect for a reactive method. Used aspect a fair bit in past projects. My struggle is with trying to aspect a reactive method which is new to me. I been attempting with @ Around from start. – Fllappy Oct 13 '20 at 18:14
  • @kriegaex Only reason I played with @ Before is based on your suggestion below and might have pushed it a bit trying to hack with it. Thus back to saying doOnSubscribe() is the crux of this question. I need to add operations within there else that operation going to happen before a subscription occurs. – Fllappy Oct 13 '20 at 18:14
  • Added Further attempt above for reference. No probs if this is still not making any sense. – Fllappy Oct 13 '20 at 18:23
  • What I was referring to by my previous comment is that I gave you hints, but received zero feedback. Instead, you are trying completely different things which do not even make sense. You neither understand my answer nor my hints nor AOP nor reactive programming. Sorry, I do not mean to offend you but to help. But without qualified feedback I cannot help you. As a last resort, please let your code speak: Provide an [MCVE](https://stackoverflow.com/help/mcve) on GitHub, so I can reproduce your problem. I do not want to guess and recreate your situation by myself. – kriegaex Oct 14 '20 at 03:57
  • Why would you add more `doOn*()` callbacks in your aspect to the already existing ones in the application code? I thought you want to intercept the existing ones. It does not make any sense to me and it is also not what I said you could try instead in my answer, section **B**. – kriegaex Oct 14 '20 at 03:59
  • Did you work with promise? Maybe you need to understand rx. When you subscribe it will trigger when data is received. Also main thread does not wait for execution process. Especially aspect prints first and when the data received from database, subscribe works. – Gurkan İlleez Mar 22 '22 at 18:47

1 Answers1

2

What is the problem?

The problem is not the aspect, it is your understanding of how and when the code you are trying to intercept is getting executed. The aspect does exactly what you tell it to: It logs something before and after the annotated method is executed. So far, so good.

What you should to do instead

What you want to intercept are the asynchronous callbacks you register in the add method configuring the behaviour for later execution. If you want to do that, you should rather intercept the code provided to doOnSubscribe and doOnEach with @Before or @After advices, depending on when you want the information printed (in your example log you seem to prefer after).

A) If you use Spring AOP

In order to do that, you cannot use lambdas in combination with Spring AOP because Spring AOP can only intercept code in Spring components. So you would have to extract both lambdas into classes (which essentially they are, just anonymous ones), make those classes Spring beans and then intercept their methods. I hope you know that a lambda is basically an anonymous class implementing an interface with a single method (not really internally in JVM byte code, but for simple understanding). So if you create separate Spring components from RxJava subclasses of Consumer, Observer or whatever your callback implements, you can intercept them via Spring AOP. But that would mean to modify your code so as to accommodate to and facilitate AOP usage. Your code would also get less terse and expressive. But you asked the question, I am just answering it.

B) If you use AspectJ

If you switch to from Spring AOP to AspectJ or have been using AspectJ already, you can try to directly target the lambdas, but this is also tricky. I would have to write a very long answer to explain why, you you can read this answer and AspectJ issue #471347 for more information.

It gets easier if you convert the lambdas to classical anonymous subclasses (a good IDE like IntelliJ IDEA should help you do that automatically for you with two mouse clicks when you ask it to), which then you can intercept via AspectJ. But again, you are then catering your programming style to AOP usage because at the moment AspectJ does not have explicit support for lambdas.


Update after question was changed

You edited your question ~1.5 years ago, about 1 week after my answer, providing additional sample code snippets (but sadly, still no MCVE. However, people who commented on or answered questions do not get notifications about question or answer edits. If you want their attention, you need to notify them in an additional comment. I just happened to open this question again today by chance, searching for something else. So even though I hope that you have solved your problem long ago, let me provide some feedback for the record, as other people might also stumble upon this question.

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

You are mixing a @Before advice with ProceedingJoinPoint, which is a JoinPoint subclass used only in @Around advice, by trying to down-cast and proceed() on it. This is doomed to fail. It is the same as trying to cast an instance of Ellipse to Circle (a specific ellipse with excentricity 0) or Rectangle to Square (a specific rectangle with four equal sides). In order to be able to use a Square, Circle or ProceedingJoinPoint, the object your are trying to cast actually needs to be one such instance, which is not the case here. The joinpoint object in a before-advice is not a proceeding joinpoint.

As for your second example, it is an @Around advice, which is fine if you wish to change the original return value. But here...

    return pjp.proceed(methodArguments)

You are trying to proceed with arguments you never defined via pjp.getArgs() before. Just like the first problem, this one is not even an AOP problem but simply using an undefined variable in Java. Of course, it cannot compile. Actually, if you do not change the arguments, there is no need to do that. Simply call proceed() without arguments, and it will use the original ones.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks. Updated the question with the attempt as per the advice under "What you should to do instead" . – Fllappy Oct 07 '20 at 14:09
  • No you did not. On Oct 7, you simply added one sentence, stating that you use AspectJ and not Spring AOP. Only a week later you added more code, but without notifying me again with a comment (so I had no change to notice other than by chance). Comparing the edit history with your comment clearly proves that. I just happened to open this question again today after 1.5 years and noticed you silently added more code. So I just updated my answer, commenting on the two new code fragments you provided. If you are still interested in why they were not working, feel free to read it. – kriegaex Mar 19 '22 at 03:28