Использование AspectJ LTW для включения функций прокси-сервера Spring при самовызове непубличных методов и сопутствующих соображений.

Я видел множество примеров функциональности Spring, связанных с @Cacheable, @Transactional, @Async и т. д., где каждый раз повторяются одни и те же параметры:

  1. Самостоятельный вызов через прокси-объект, полученный либо через ApplicationContext.getBean(MyService.class), либо через автоматически подключенный MyService.class прокси-объект в дополнение к @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),

  2. Перемещение целевого метода в отдельный класс @Service,

  3. Использование переплетения времени загрузки AspectJ.

Хотя первые два подхода обычно хороши, бывают случаи, когда нам нужно прикрепить функциональность трех вышеперечисленных (и других) аннотаций к закрытым методам, будь то для ясности кода, дизайна или по другим причинам.


Есть много примеров первых двух подходов, но очень мало последних. Насколько я понимаю, из-за характера AspectJ LTW по умолчанию он является взаимоисключающим с обычным поведением Spring AOP, которое позволяет без особых хлопот использовать поведение @Cacheable и т. д. Мои вопросы заключаются в следующем:

  1. Есть ли достойные примеры включения вышеуказанного поведения, обычно выполняемого с первыми двумя вариантами с использованием AspectJ LTW?

  2. Есть ли способ выборочно включить AspectJ LTW, например не для @Async и @Transactional а только @Cacheable? Примером использования этого может быть: из-за дизайна команды все кэшируемые методы (некоторые из которых могут быть закрытыми) должны быть расположены в фасадном классе, который вызывает частные методы, которые выполняют тяжелые вычисления, но могут обновлять некоторое состояние (т.е. 'last-queried-at: #') перед возвратом к внешнему вызову.

Этот вопрос с точки зрения пользователя spring-boot, но я считаю, что он в целом применим к Spring AOP и AspectJ LTW. Пожалуйста, поправьте меня, если в этом случае необходимы особые соображения.


person user991710    schedule 28.01.2018    source источник
comment
AspectJ превосходит решение Spring AOP на основе прокси практически во всех аспектах, поэтому нет смысла пытаться использовать оба одновременно. После того, как вы включили AspectJ LTW для своего приложения, вы должны отключить материал на основе прокси, удалив @EnableAspectJAutoProxy и настроив свои аспекты с помощью @Bean фабричных методов, если у вас есть аспекты, которые необходимо настроить к весне.   -  person Nándor Előd Fekete    schedule 29.01.2018
comment
Какие усилия потребуются для переключения существующих @Cacheable, @Transactional, @Async, а также прокси-функций Spring AOP на AspectJ? Существуют ли какие-либо доступные примеры, демонстрирующие этот вариант использования (даже лучше, если есть рассмотрение плюсов и минусов)?   -  person user991710    schedule 30.01.2018
comment
Не так много усилий. Попробуйте сделать то, что я сказал в предыдущем комментарии. Не уверен, что вы подразумеваете под функциональностью прокси Spring AOP, но AspectJ делает больше, чем это, по-другому, но лучше, поэтому, если вы переключите Spring AOP на AspectJ, это будет лучше. Единственное, что, вероятно, потребует дополнительной работы, — это поддержка старого способа определения pointcuts и советов в конфигурации xml для Spring AOP. @Cacheable, @Transactional и @Async поддерживаются аспектами в модуле spring-aspects.   -  person Nándor Előd Fekete    schedule 05.02.2018
comment
Пожалуйста, подумайте немного о том, кто будет поддерживать этот код после вас. Будут ли они благодарны вам за всю дополнительную сложность, привносимую аспектами? Будет ли проще (или вообще возможно) отлаживать этот код?   -  person Devstr    schedule 10.02.2018


Ответы (1)


  1. Есть ли достойные примеры включения вышеуказанного поведения, обычно выполняемого с первыми двумя вариантами с использованием AspectJ LTW?

У меня есть пара примеров AspectJ в моей учетной записи GitHub. В обоих этих примерах показано, как перехватывать вызовы внутри одного и того же целевого объекта (самовызов), а также перехватывать частные методы.

  1. Пример создания исходного кода Spring Boot с AspectJ
  2. Пример плетения Spring Boot во время загрузки с AspectJ

Оба примера похожи, за исключением того, как аспекты вплетены в целевые классы.

Пожалуйста, прочитайте README примеров, чтобы узнать больше о каждом типе плетения и о том, как использовать каждый из примеров.

  1. Есть ли способ выборочно включить AspectJ LTW, например не для @Async и @Transactional, а только для @Cacheable?

Да, вы можете фильтровать по одному из следующих параметров:

  • По типу аннотации вызывающего метода.

    @Before("call(* com.basaki.service.UselessService.sayHello(..))" +
            "  && cflow(@annotation(trx))")
    public void inspectMethod(JoinPoint jp,
            JoinPoint.EnclosingStaticPart esjp, Transactional trx) {
        log.info(
                "Entering FilterCallerAnnotationAspect.inspectMethod() in class "
                        + jp.getSignature().getDeclaringTypeName()
                        + " - method: " + jp.getSignature().getName());
    }
    
  • По имени вызывающего метода.

    @Before("call(* com.basaki.service.UselessService.sayHello(..))" +
            "  && cflow(execution(* com.basaki.service.BookService.read(..)))")
    public void inspectMethod(JoinPoint jp,
            JoinPoint.EnclosingStaticPart esjp) {
        log.info(
                "Entering FilterCallerMethodAspect.inspectMethod() in class "
                        + jp.getSignature().getDeclaringTypeName()
                        + " - method: " + jp.getSignature().getName());
    }
    

Вы можете найти рабочие примеры здесь.

Обновлено

Вопрос. Правильно ли я понимаю, что если бы я хотел включить переплетение во время компиляции для обеспечения транзакционности, я бы: 1. Больше не использовал TransactionAwareDataSourceProxy в моей конфигурации DataSource; 2. Добавьте в мое приложение следующее: @EnableTransactionManagement(mode=AdviceMode.ASPECTJ).

Переплетения Spring AOP и CTW/LTW AspectJ полностью ортогональны, т. е. независимы друг от друга.

  • Компиляция и LTW изменяют фактический байт-код, т. е. строки кода вставляются в тело метода целевого объекта.
  • АОП основан на прокси, т. е. вокруг целевого объекта существует оболочка. Любой вызов целевого объекта перехватывается объектом-оболочкой Spring. Вы также можете использовать Spring AOP с CTW/LTW, если возникнет такая необходимость. В этом случае Spring AOP сделает прокси измененной цели.

Вам понадобится @EnableTransactionManagement, если вы хотите включить возможность управления транзакциями Spring на основе аннотаций.

В. В ваших примерах я вижу, что вы не запускаете приложение каким-то особенным образом для CTW. Этого будет достаточно, или я что-то пропустил?

Да, в CTW вам не нужно ничего особенного во время запуска, так как дополнительный байт-код уже вставлен в исходный код компилятором AspectJ (ajc) во время компиляции. Например, вот оригинальный исходный код:

@CustomAnnotation(description = "Validates book request.")
private Book validateRequest(BookRequest request) {
    log.info("Validating book request!");

    Assert.notNull(request, "Book request cannot be empty!");
    Assert.notNull(request.getTitle(), "Book title cannot be missing!");
    Assert.notNull(request.getAuthor(), "Book author cannot be missing!");

    Book entity = new Book();
    entity.setTitle(request.getTitle());
    entity.setAuthor(request.getAuthor());

    return entity;
}

Вот тот же фрагмент кода после компиляции компилятором AspectJ, ajc:

private Book validateRequest(BookRequest request) {
    JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, request);
    CustomAnnotationAspect var10000 = CustomAnnotationAspect.aspectOf();
    Annotation var10002 = ajc$anno$0;
    if (ajc$anno$0 == null) {
        var10002 = ajc$anno$0 = BookService.class.getDeclaredMethod("validateRequest", BookRequest.class).getAnnotation(CustomAnnotation.class);
    }

    var10000.inspectMethod(var3, (CustomAnnotation)var10002);

    log.info("Validating book request!");
    Assert.notNull(request, "Book request cannot be empty!");
    Assert.notNull(request.getTitle(), "Book title cannot be missing!");
    Assert.notNull(request.getAuthor(), "Book author cannot be missing!");

    Book entity = new Book();
    entity.setTitle(request.getTitle());
    entity.setAuthor(request.getAuthor());
    return entity;
}

Находясь в LTW, вам нужен агент Java, так как код изменяется во время загрузки, т. е. когда классы загружаются загрузчиками классов Java.

person Indra Basak    schedule 10.02.2018
comment
Отличный материал, большое спасибо за ваш вклад! Правильно ли я понимаю, что если бы я хотел включить переплетение времени компиляции для транзакционности, я бы: 1. Больше не использовал TransactionAwareDataSourceProxy где-либо в моей конфигурации DataSource; 2. Добавьте в мое приложение следующее: @EnableTransactionManagement(mode=AdviceMode.ASPECTJ). В ваших примерах я вижу, что вы не запускаете приложение каким-то особым образом для CTW. Этого будет достаточно, или я что-то пропустил? - person user991710; 13.02.2018
comment
Обновлена ​​моя запись с ответами на ваши вопросы. - person Indra Basak; 14.02.2018
comment
Большое спасибо за ваш подробный ответ. Думаю, это все проясняет. - person user991710; 15.02.2018
comment
Было бы здорово, если бы вы добавили производительность всех трех подходов, я имею в виду CTW, LTW и прокси. - person Anil Gowda; 11.08.2020