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()));
}
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 todoOnSubscribe
anddoOnEach
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.
You are mixing a
@Before
advice withProceedingJoinPoint
, which is aJoinPoint
subclass used only in@Around
advice, by trying to down-cast andproceed()
on it. This is doomed to fail. It is the same as trying to cast an instance ofEllipse
toCircle
(a specific ellipse with excentricity 0) orRectangle
toSquare
(a specific rectangle with four equal sides). In order to be able to use aSquare
,Circle
orProceedingJoinPoint
, 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...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 callproceed()
without arguments, and it will use the original ones.