Вызов аспекта завершается до того, как метод будет подписан

У меня есть метод, в котором есть реактивный код (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

Это возможно?

Это самый близкий вопрос, который я нашел по этому поводу. Написание аспектов для реактивных конвейеров

Но этот вопрос основан на Spring. Также в этом ответе есть строка:

Вы должны сообщить аспекту об асинхронной ситуации.

Похоже, это то, что мне нужно сделать, но как я могу это сделать? Спасибо за любой совет.

--- ОБНОВЛЕНИЕ после предложения ---

Отметим, что я использую AspectJ, а не Spring.

Это не работает, потому что перед подпиской переменная continue имеет значение null.

Таким образом, я добавил нулевую проверку. Но мы собираемся войти сюда только один раз.

Таким образом, continue.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()));
}

person Fllappy    schedule 04.10.2020    source источник
comment
В исходном вопросе вы использовали совет @Around. Здесь подходят ProceedingJoinPoint jp и jp.proceed(). Второй аспект после вашего редактирования показывает совет @Before. Там вы должны использовать обычный JoinPoint, и вы никогда не вызываете proceed(), потому что, если вы не создадите исключение в совете, целевой метод будет выполнен автоматически после этого. Ваша попытка бросить и продолжить показывает, что вы этого не понимаете. Вы действительно должны прочитать некоторую документацию, а не просто взломать методом проб и ошибок.   -  person kriegaex    schedule 08.10.2020
comment
@kriegaex Читал документы. Вот почему я раньше использовал JoinPoint для @. Поймите вашу точку зрения о том, чтобы не использовать ProceedingJoinPoint здесь. Приведенные выше примеры будут нормально работать в обычном сценарии. Я застрял, не понимая, как я могу сделать doOnSubscribe внутри метода before здесь. Это не описано в документации, по крайней мере, я не видел.   -  person Fllappy    schedule 08.10.2020
comment
Затем снова прочитайте документацию. Нет смысла пытаться преобразовать обычную точку соединения из совета «до» в точку соединения «продолжение», которая, как ожидается, будет использоваться в совете «вокруг». Звонить proceed() еще более безумно. Я на 100% уверен, что вы никогда не найдете этого ни в одной документации, учебнике или примере кода. Это просто неправильно. Прежде чем беспокоиться о doOnsubscribe(), узнайте, как использовать AspectJ. Это было моей точкой зрения.   -  person kriegaex    schedule 08.10.2020
comment
@kriegaex никогда бы не стал беспокоиться о ProceedingJoinPoint в @ Раньше, если бы я не беспокоился о том, что doOnsubscribe() пытается увидеть, есть ли обходной путь, чтобы заставить его работать. В любом случае спасибо за ответ.   -  person Fllappy    schedule 08.10.2020
comment
Я не понимаю. Если вы просто хотите зафиксировать результат и что-то записать, используйте @AfterReturning, а не @Before. Вы хотите сделать что-то после возврата метода, а не до. Если вы хотите изменить возвращаемое значение и/или сделать что-то до + после исходного метода, используйте @Around. Вы не можете читать какую-либо документацию, если не знаете этого. Я действительно пытаюсь помочь здесь. В третий и последний раз ваша проблема не имеет ничего общего с doOnSubscribe(). Вам нужно практиковаться и понимать АОП.   -  person kriegaex    schedule 08.10.2020
comment
Вы отказались от этого? Нет больше обратной связи? Он до сих пор в моем списке последующих действий.   -  person kriegaex    schedule 13.10.2020
comment
@kriegaex В третий и последний раз. Не было впечатления, что я собирался получить какие-либо дальнейшие отзывы после этого. Вы уже не раз упомянули, что мне нужно начать читать аспекты прямо сейчас. Справедливое замечание, я не эксперт. Но моя проблема конкретно в том, как использовать аспект для реактивного метода. Использовался аспект в прошлых проектах. Моя борьба связана с попыткой аспектировать реактивный метод, который является новым для меня. Я пытался использовать @Around с самого начала.   -  person Fllappy    schedule 13.10.2020
comment
@kriegaex Единственная причина, по которой я играл с @ Before, основана на вашем предложении ниже и, возможно, немного подтолкнула его к попытке взломать его. Таким образом, вернемся к тому, что doOnSubscribe() является сутью этого вопроса. Мне нужно добавить операции внутри, иначе эта операция произойдет до того, как произойдет подписка.   -  person Fllappy    schedule 13.10.2020
comment
Добавлена ​​дополнительная попытка выше для справки. Нет проблем, если это все еще не имеет никакого смысла.   -  person Fllappy    schedule 13.10.2020
comment
В своем предыдущем комментарии я имел в виду, что я давал вам подсказки, но не получил никакой обратной связи. Вместо этого вы пробуете совершенно другие вещи, которые даже не имеют смысла. Вы не понимаете ни моего ответа, ни моих намеков, ни АОП, ни реактивного программирования. Извините, не хочу вас обидеть, а помочь. Но без квалифицированной обратной связи я не могу вам помочь. В крайнем случае, пусть ваш код говорит: предоставьте MCVE на GitHub, чтобы я мог воспроизвести вашу проблему. Я не хочу сам догадываться и воссоздавать вашу ситуацию.   -  person kriegaex    schedule 14.10.2020
comment
Зачем вам добавлять больше doOn*() обратных вызовов в свой аспект к уже существующим в коде приложения? Я думал, ты хочешь перехватить существующие. Для меня это не имеет никакого смысла, и я также не сказал, что вы можете попробовать вместо этого в моем ответе, раздел B.   -  person kriegaex    schedule 14.10.2020


Ответы (1)


В чем проблема?

Проблема не в аспекте, а в вашем понимании того, как и когда выполняется код, который вы пытаетесь перехватить. Аспект делает именно то, что вы ему говорите: он регистрирует что-то до и после выполнения аннотированного метода. Все идет нормально.

Что вы должны делать вместо этого

То, что вы хотите перехватить, — это асинхронные обратные вызовы, которые вы регистрируете в методе add, настраивающем поведение для последующего выполнения. Если вы хотите это сделать, вам лучше перехватить код, предоставленный doOnSubscribe и doOnEach, с советами @Before или @After, в зависимости от того, когда вы хотите распечатать информацию (в вашем примере журнала вы, кажется, предпочитаете после).

А) Если вы используете Spring АОП

Для этого вы не можете использовать лямбда-выражения в сочетании с Spring AOP, потому что Spring AOP может перехватывать код только в компонентах Spring. Таким образом, вам придется извлечь оба лямбда-выражения в классы (которые, по сути, они и есть, просто анонимные), сделать эти классы Spring bean-компонентами, а затем перехватить их методы. Я надеюсь, вы знаете, что лямбда — это, по сути, анонимный класс, реализующий интерфейс с одним методом (на самом деле не внутри байт-кода JVM, а для простого понимания). Поэтому, если вы создаете отдельные компоненты Spring из подклассов RxJava Consumer, Observer или чего-то еще, реализуемого вашим обратным вызовом, вы можете перехватить их через Spring AOP. Но это означало бы изменить ваш код так, чтобы он соответствовал и облегчал использование АОП. Ваш код также станет менее кратким и выразительным. Но вы задали вопрос, я просто отвечаю на него.

Б) Если вы используете AspectJ

Если вы переключаетесь с Spring AOP на AspectJ или уже используете AspectJ, вы можете попытаться напрямую ориентироваться на лямбда-выражения, но это тоже сложно. Мне пришлось бы написать очень длинный ответ, чтобы объяснить почему, вы можете прочитать этот ответ и AspectJ issue #471347 для получения дополнительной информации.

Это становится проще, если вы конвертируете лямбды в классические анонимные подклассы (хорошая IDE, такая как IntelliJ IDEA, должна помочь вам сделать это автоматически двумя щелчками мыши, когда вы попросите об этом), которые затем вы можете перехватить через AspectJ. Но опять же, вы приспосабливаете свой стиль программирования к использованию АОП, потому что на данный момент AspectJ не имеет явной поддержки лямбда-выражений.

person kriegaex    schedule 05.10.2020
comment
Спасибо. Обновлен вопрос с попыткой в ​​​​соответствии с советом в разделе « Что делать вместо этого» . - person Fllappy; 07.10.2020