Использование AspectJ LTW для разрешения функции Spring Proxy при самостоятельном вызове закрытых методов и связанных с этим соображений
Я видел множество примеров функций Spring, связанных с @Cacheable
, @Transactional
, @Async
и т. д., где каждый раз повторяются одни и те же параметры:
Самостоятельный вызов через прокси-объект, полученный через
ApplicationContext.getBean(MyService.class)
или с автопроводкойMyService.class
прокси-объект в дополнение к@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
,Перемещение целевого метода в отдельный
@Service
учебный класс,Использование AspectJ время загрузки.
Хотя первые два подхода обычно хороши, бывают случаи, когда нам необходимо присоединить функциональность трех вышеупомянутых (и других) аннотаций к частным методам, будь то для ясности кода, дизайна или по другим причинам.
Есть много примеров первых двух подходов, но очень мало последних. Как я понимаю, из-за природы AspectJ LTW по умолчанию он является взаимоисключающим с обычным поведением Spring AOP, которое позволяет @Cacheable
и т.д. поведение без особых хлопот. Мои вопросы следующие:
Существуют ли приличные примеры включения описанного выше поведения, обычно выполняемые с двумя первыми опциями с использованием AspectJ LTW?
Есть ли способ включить AspectJ LTW выборочно, например, не для
@Async
а также@Transactional
но просто@Cacheable
? Примером использования этого может быть: из-за дизайна команды все кэшируемые методы (некоторые из которых могут быть частными) должны быть расположены в классе фасада, который вызывает частные методы, которые выполняют тяжелые вычисления, но могут обновлять некоторое состояние (то есть'last-queried-at: #'
) перед возвращением на внешний звонил.
Этот вопрос с точки зрения spring-boot
пользователь, но я считаю, что в целом это относится к Spring AOP и AspectJ LTW. Пожалуйста, поправьте меня, если в этом случае необходимы особые соображения.
1 ответ
- Существуют ли приличные примеры включения описанного выше поведения, обычно выполняемые с двумя первыми опциями с использованием AspectJ LTW?
У меня есть несколько примеров AspectJ на моей учетной записи GitHub. Оба эти примера показывают, как перехватывать вызовы внутри одного и того же целевого объекта (самовывоз), а также перехватывать частные методы.
- Пример создания исходных текстов для весенней загрузки с AspectJ
- Пример ткачества времени загрузки Spring Boot с AspectJ
Оба примера похожи, за исключением того, как аспекты сплетаются в целевые классы.
Пожалуйста, прочтите README примеров, чтобы узнать больше о каждом типе ткачества и о том, как использовать каждый из примеров.
- Есть ли способ выборочно включить 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.