Aspektaufruf wird beendet, bevor die Methode abonniert wird
Ich habe eine Methode, die reaktiven Code enthält (RxJava).
Ich habe einen Aspect @around darum gewickelt.
Das Setup ist in Ordnung, es wird wie folgt ausgedruckt.
Dies geschieht jedoch, bevor die Methode überhaupt abonniert wurde.
Gibt es eine Möglichkeit, wie wir es so einrichten können, dass der Aspekt erst startet, nachdem die Methode abonniert wurde?
Meine Aspektklasse
@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;
}
}
Die benutzerdefinierte Anmerkung
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SomeLogger {
String name() default "";
}
Die Methode, die mit Anmerkungen versehen wird, um den Aspekt zu verwenden.
@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()));
}
Wie bereits erwähnt, werden die Druckzeilen in der Aspektklasse bereits vor dem Abonnement gedruckt, was falsch ist.
Dies ist also die aktuell falsche Reihenfolge für den Druck.
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
Ich hatte die folgende Bestellung erwartet.
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
Ist das möglich?
Dies ist die naheliegendste Frage, die ich diesbezüglich gefunden habe. Schreiben von Aspekten für reaktive Pipelines
Aber diese Frage basiert auf dem Frühling. Auch diese Antwort enthält eine Zeile:
Sie müssen den Aspekt auf die asynchrone Situation aufmerksam machen.
Klingt so, als müsste ich das tun, aber wie kann ich das tun? Vielen Dank für jeden Rat.
--- UPDATE nach Vorschlag ---
Zu beachten ist, ich verwende AspectJ nicht Spring.
Dies funktioniert nicht, da die Variable continue vor dem Abonnement null ist.
Daher habe ich einen Null-Check hinzugefügt. Aber wir werden hier nur einmal eintreten.
So geht continue.doOnSubscribe () nie vor.
@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");
}
Weiterer Versuch:
Zumindest theoretisch hatte man damit gerechnet. Aber wirft AJC Compiler Fehler.
@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()));
}
Antworten
Worin besteht das Problem?
Das Problem ist nicht der Aspekt, sondern Ihr Verständnis, wie und wann der Code, den Sie abfangen möchten, ausgeführt wird. Der Aspekt macht genau das, was Sie ihm sagen: Er protokolliert etwas vor und nach der Ausführung der mit Anmerkungen versehenen Methode. So weit, ist es gut.
Was sollten Sie stattdessen tun?
Was Sie abfangen möchten, sind die asynchronen Rückrufe, die Sie in der addMethode registrieren, die das Verhalten für die spätere Ausführung konfiguriert. Wenn Sie dies tun möchten, sollten Sie lieber den Code abfangen, der für doOnSubscribeund doOnEachmit @Beforeoder für @AfterRatschläge bereitgestellt wird , je nachdem, wann die Informationen gedruckt werden sollen (in Ihrem Beispielprotokoll scheinen Sie danach zu bevorzugen).
A) Wenn Sie Spring AOP verwenden
Zu diesem Zweck können Sie Lambdas nicht in Kombination mit Spring AOP verwenden, da Spring AOP nur Code in Spring-Komponenten abfangen kann. Sie müssten also beide Lambdas in Klassen extrahieren (die im Wesentlichen nur anonyme sind), diese Klassen zu Spring Beans machen und dann ihre Methoden abfangen. Ich hoffe, Sie wissen, dass ein Lambda im Grunde eine anonyme Klasse ist, die eine Schnittstelle mit einer einzigen Methode implementiert (nicht wirklich intern im JVM-Bytecode, aber zum einfachen Verständnis). Also , wenn Sie getrennte Federkomponenten aus RxJava Subklassen von erstellen Consumer, Observeroder was auch immer Ihre Callback - Geräte, können Sie sie über Frühling AOP abfangen. Dies würde jedoch bedeuten, Ihren Code so zu ändern, dass er der AOP-Nutzung entspricht und diese erleichtert. Ihr Code würde auch weniger knapp und ausdrucksstark werden. Aber Sie haben die Frage gestellt, ich beantworte sie nur.
B) Wenn Sie AspectJ verwenden
Wenn Sie von Spring AOP zu AspectJ wechseln oder AspectJ bereits verwendet haben, können Sie versuchen, die Lambdas direkt anzuvisieren. Dies ist jedoch auch schwierig. Ich müsste eine sehr lange Antwort schreiben, um zu erklären, warum. Sie können diese Antwort und die AspectJ-Ausgabe Nr. 471347 lesen, um weitere Informationen zu erhalten.
Es wird einfacher, wenn Sie die Lambdas in klassische anonyme Unterklassen konvertieren (eine gute IDE wie IntelliJ IDEA sollte Ihnen dabei helfen, dies automatisch mit zwei Mausklicks zu tun, wenn Sie dazu aufgefordert werden), die Sie dann über AspectJ abfangen können. Aber auch hier richten Sie Ihren Programmierstil auf die AOP-Nutzung aus, da AspectJ derzeit keine explizite Unterstützung für Lambdas bietet.