Разница между @target и @within (Spring AOP)

Руководство Spring говорит:

any join point (method execution only in Spring AOP) where the target object has an @Transactional annotation: @target(org.springframework.transaction.annotation .Transactional)

any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation: @within(org.springframework.transaction.annotation .Transactional)

Но я не вижу разницы между ними!

Я попробовал погуглить:

One difference between the two is that @within() is matched statically, requiring the corresponding annotation type to have only the CLASS retention. Whereas, @target() is matched at runtime, requiring the same to have the RUNTIME retention. Other than that, within the context of Spring, here is no difference between the join points selected by two.

Поэтому я попытался добавить пользовательскую аннотацию с сохранением КЛАСС, но Spring выдает исключение (из-за удержания аннотации должны бытьВРЕМЯ РАБОТЫ)

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
11
0
8 488
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Приведенная вами информация верна, однако только точечные обозначения @target требуют аннотаций с удерживанием RUNTIME, в то время как @within требует только удержания CLASS.

Рассмотрим две следующие простые аннотации:

ClassRetAnnotation.java

package mypackage;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.CLASS)
public @interface ClassRetAnnotation {}

RuntimeRetAnnotation.java

package mypackage;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeRetAnnotation {}

Теперь, если вы определите аспект, подобный следующему, во время выполнения не будет исключений:

@Component
@Aspect
public class MyAspect {

    @Before("@within(mypackage.ClassRetAnnotation)")
    public void within() { System.out.println("within"); }

    @Before("@target(mypackage.RuntimeRetAnnotation)")
    public void target() { System.out.println("target"); }
}

Надеюсь, этот пример помог прояснить ту тонкую разницу, на которую вы указали.

Ссылка на пружину: https://docs.spring.io/spring/docs/5.0.x/spring-framework-reference/core.html#aop-pointcuts

Я не понимаю, как это отвечает на вопрос ОП.

kriegaex 02.07.2018 02:46

@kriegaex OP указал, что Spring выдает исключение, потому что аннотация должна сохранять RUNTIME; Я хотел продемонстрировать, что это верно только для указателя @target. Мне очень нравится ваш подробный ответ, в любом случае я заметил, что контекст вопроса явно ограничен Spring AOP, поэтому мой ответ ограничен проблемой, о которой сообщает OP. Ваше здоровье.

Robert Hume 02.07.2018 05:57

Вы правы относительно объема вопроса и своего ответа. Но поскольку Spring AOP заимствует оба дескриптора pointcut из AspectJ, и они ведут себя идентично (за исключением небольшой разницы в хранении аннотаций), я думаю, что действительно было необходимо расширить область действия, чтобы сначала получить общую картину, прежде чем сосредоточиться на том, что осталось от этого. большая картина в Spring AOP. Мой последний совет (здесь нет каламбура) также содержит подсказку о том, почему знание всего этого может быть важно для пользователей Spring AOP: многие из них переходят на AspectJ позже, чтобы преодолеть ограничения Spring AOP.

kriegaex 02.07.2018 06:10
(продолжение) После миграции они замечают, что аспекты ведут себя по-другому, но из-за того, что они не понимают Зачем и насколько легко приручить аспекты AspectJ, они быстро сдаются, возвращаясь к Spring AOP, используя ужасные обходные пути для вещей, которые можно сделать красиво и без проблем. простые способы с AspectJ.
kriegaex 02.07.2018 06:12

@kriegaex Я согласен, и конечно, они ведут себя идентично в Spring AOP! Еще раз спасибо, я только что оставил свои два цента вместе с ответом.

Robert Hume 02.07.2018 06:17
Ответ принят как подходящий

Вы не замечаете никакой разницы, потому что Spring AOP, используя синтаксис AspectJ, фактически эмулирует только ограниченное подмножество своей функциональности. Поскольку Spring AOP основан на динамических прокси, он обеспечивает только перехват выполнения общедоступных нестатических методов. (При использовании прокси CGLIB вы также можете перехватывать методы с охватом пакета и защищенные методы.) AspectJ, однако, также может перехватывать вызовы методов (не только выполнение), доступ к полям-членам (как статические, так и нестатические), вызов / выполнение конструктора, статический класс инициализация и многое другое.

Итак, давайте создадим очень простой пример AspectJ:

Аннотация маркера:

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}

Приложение драйвера:

package de.scrum_master.app;

@MyAnnotation
public class Application {
  private int nonStaticMember;
  private static int staticMember;

  public void doSomething() {
    System.out.println("Doing something");
    nonStaticMember = 11;
  }

  public void doSomethingElse() {
    System.out.println("Doing something else");
    staticMember = 22;
  }

  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething();
    application.doSomethingElse();
  }
}

Аспект:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
  @Before("@within(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
  public void adviceAtWithin(JoinPoint thisJoinPoint) {
    System.out.println("[@within] " + thisJoinPoint);
  }

  @Before("@target(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
  public void adviceAtTarget(JoinPoint thisJoinPoint) {
    System.out.println("[@target] " + thisJoinPoint);
  }
}

Обратите внимание, что здесь я имитирую поведение Spring AOP, добавляя && execution(public !static * *(..)) в оба pointcut.

Журнал консоли:

[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else

Здесь нет ничего удивительного. Это именно то, что вы также увидите в Spring AOP. Теперь, если вы удалите часть && execution(public !static * *(..)) из обоих pointcut, в Spring AOP результат останется таким же, но в AspectJ (например, также, если вы активируете AspectJ LTW в Spring), он изменится на:

[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] initialization(de.scrum_master.app.Application())
[@target] initialization(de.scrum_master.app.Application())
[@within] execution(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@within] call(void de.scrum_master.app.Application.doSomething())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@within] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)

При детальном рассмотрении можно увидеть, что перехватывается гораздо больше точек соединения @within(), но также еще несколько точек соединения @target(), например упомянутые ранее точки соединения call(), а также set() для нестатических полей и объекта initialization(), происходящие до выполнения конструктора.

Если посмотреть только на @target(), мы увидим следующее:

[@target] initialization(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else

Для каждой из этих выходных строк формата мы также видим соответствующее совпадение @within(). Теперь давайте сконцентрируемся на том, что не одинаково, и отфильтруем вывод на предмет различий:

[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)

Вот видите, в порядке появления

  • инициализация статического класса,
  • выполнение статического метода,
  • вызов конструктора (еще не исполнен!),
  • предварительная инициализация построенного объекта,
  • доступ к переменной-члену в другом классе (System.out),
  • вызов метода из другого класса (PrintStream.println(String)),
  • установка статического члена класса.

Что общего у всех этих pointcut? Целевого объекта нет, потому что либо мы говорим о статических методах или членах, инициализации статического класса, предварительной инициализации объекта (еще не определен this) или вызове / доступе к материалам из других классов, не несущих аннотации, на которую мы нацелены.

Итак, вы видите, что в AspectJ есть существенные различия между двумя pointcut, в Spring AOP они просто не заметны из-за его ограничений.

Мой совет - используйте @target(), если вы намерены перехватить нестатическое поведение в экземпляре целевого объекта. Это упростит переключение на AspectJ, если вы когда-нибудь решите активировать режим AspectJ в Spring или даже перенести какой-либо код в приложение с поддержкой аспектов, отличное от Spring.

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