Использование AspectJ LTW для разрешения функции Spring Proxy при самостоятельном вызове закрытых методов и связанных с этим соображений

Я видел множество примеров функций 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. Пожалуйста, поправьте меня, если в этом случае необходимы особые соображения.

1 ответ

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

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

  1. Пример создания исходных текстов для весенней загрузки с 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());
    }
    

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

обновленный

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

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

  • Компиляция и LTW изменяют фактический байт-код, то есть строки кода вставляются в тело метода целевого объекта.
  • AOP основан на прокси, то есть вокруг целевого объекта есть оболочка. Любой вызов целевого объекта перехватывается объектом-оболочкой 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.

Другие вопросы по тегам