Неясный приоритет рекомендаций при объединении рекомендаций до, вокруг и после, работающих с одной и той же точкой соединения в одном аспекте

#java #aop #aspectj

#java #aop #aspectj

Вопрос:

Пожалуйста, рассмотрите этот простой Java-код

 public class Application {

  public void m(int i) {
   System.out.println("M with argument "   i );
  }

  public static void main(String[] arg) {
   Application t = new Application();
   t.m(25);  
  }
}
  

Я определил следующий аспект для работы с этим классом:

 public aspect Basics {
  public void output(String tag, Object o) {
    System.out.println(tag   ": "   o);
  }

  pointcut callM(int i): call(void Application.m(int)) amp;amp; args(i);

  before(int i): callM(i) {
    output("before M", i);  
  }

  void around(int i): callM(i) {
      output("around-advice", i);
      proceed(1);
      output("after proceed", i);
  }

  after(int i): callM(i) {
    output("After M", i);  
  }
}
  

Важно отметить, что рекомендация «вокруг» изменяет значение аргумента, переданного методу M, на 1.
Выполнение этого кода генерирует следующий вывод:

 before M: 25
around-advice: 25
M with argument 1
after proceed: 25
After M: 25
  

Весь результат такой, как я и ожидал, за исключением последней строки. Я ожидал, что в последней строке будет напечатано «1» вместо «25». Может кто-нибудь объяснить мне, почему это так?

Сам ища ответ, я попытался изменить порядок советов, но в итоге это только усилило путаницу. Если я сначала добавлю в код совет после, за которым следует совет до, а затем последний совет вокруг (т. Е. (1) после-(2) до- (3) вокруг), я получу следующий результат:

 before M: 25
around-advice: 25
M with argument 1
After M: 1
after proceed: 25
  

Для меня это единственный вывод, который имеет смысл.

Однако, если я сначала добавлю совет после, а затем совет вокруг, а последний совет до (т. Е. (1) после (2) вокруг (3) до), я получаю следующий вывод, который также не имеет для меня особого смысла, если я беру выводпредыдущие упорядочения с учетом:

 around-advice: 25
before M: 1
M with argument 1
After M: 1
after proceed: 25
  

В этом случае рекомендация «до» запускается с привязкой «i» к 1. Я предполагаю, что это связано с тем, что сначала запускается рекомендация «вокруг» (из-за упорядочения) и что рекомендация «до» фактически запускается вызовом «продолжить» в теле рекомендации «вокруг». Однако следование этой логике не объясняет результат, который был сгенерирован в порядке, который обсуждался первым в этом вопросе.

Наконец, изменение порядка таким образом, чтобы сначала у нас был совет до, за которым следует совет после, а затем последует совет вокруг (т. Е. (1) до-(2) после- (3) вокруг), недопустимо в соответствии с AspectJ-плагиномEclipse, потому что это генерирует «циклический приоритет рекомендаций».

Может кто-нибудь дать мне объяснение приоритета, используемого между различными советами в одном и том же аспекте, который объясняет все поведение выше?

Я читал здесь по этому вопросу, но я думаю, что объяснение неубедительно / не соответствует реализации. В нем говорится

Часть рекомендаций «вокруг» определяет, будет ли выполняться рекомендация с более низким приоритетом при вызове proceed . Вызов для продолжения выполнит рекомендации со следующим приоритетом или вычисления в точке соединения, если дальнейших рекомендаций нет.

Если я правильно понимаю, это означает, что вывод, который обсуждался первым в этом вопросе (т. Е. (1) до- (2) вокруг- (3) после упорядочения), должен был иметь «1» в последней строке, а не «25».

Ответ №1:

На самом деле первый набросок @XGouchet не совсем корректен, потому что совет «до» не встречается в рамках around , но завершен до выполнения around . Позвольте мне использовать обозначение псевдокода с фигурными скобками, выражающее такого рода «лексическую область»:

Сценарий A: лексический порядок до -> вокруг -> после:

 before(25)
around(251) {
    joinpoint(1)
}
after(25)
  

Сценарий B: лексический порядок после -> до -> вокруг:

 before(25)
around(251) {
    joinpoint(1)
    after(1)
}
  

Сценарий C: лексический порядок после -> вокруг -> до:

 around(251) {
    before(1)
    joinpoint(1)
    after(1)
}
  

Цитата из параграфа определения приоритета из главы руководства AspectJ о приоритете аспектов:

Если две рекомендации определены в одном и том же аспекте, то есть два случая:

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

Теперь также имейте в виду, как ведут себя аспекты и что на самом деле означает приоритет при определенных обстоятельствах. Также цитируется параграф «Влияние приоритета» из главы руководства AspectJ о приоритете аспектов:

В конкретной точке соединения рекомендации упорядочиваются по приоритету.

  • Часть рекомендаций «вокруг» определяет, будет ли выполняться рекомендация с более низким приоритетом при вызове proceed . Вызов для продолжения выполнит рекомендации со следующим приоритетом или вычисления в точке соединения, если дальнейших рекомендаций нет.
  • Часть рекомендаций «до» может предотвратить выполнение рекомендаций с более низким приоритетом, вызвав исключение. Однако, если он возвращается нормально, тогда будет выполняться рекомендация следующего приоритета или вычисление под точкой соединения, если дальнейших рекомендаций нет.
  • Запуск после возврата рекомендаций приведет к выполнению рекомендаций следующего приоритета или вычислений в точке соединения, если дальнейших рекомендаций нет. Затем, если это вычисление вернулось нормально, тело рекомендации будет выполнено.
  • Запуск после выдачи рекомендаций приведет к выполнению рекомендаций следующего приоритета или вычислений под точкой соединения, если дальнейших рекомендаций нет. Затем, если это вычисление выдало исключение соответствующего типа, будет выполняться тело рекомендации.
  • Запуск после рекомендации приведет к выполнению рекомендаций следующего приоритета или вычислений в точке соединения, если дальнейших рекомендаций нет. Затем будет выполнено тело рекомендации.

На простом английском:

  • Обтекание рекомендаций

    • другие рекомендации «вокруг» или «до» с более низким приоритетом, а также
    • после советов с более высоким приоритетом.

    Почему это так? Представьте, что совет «вокруг» представляет собой пару советов «до» и «после» на стероидах (может изменять параметры метода и возвращаемое значение, может обрабатывать исключения, может разделять состояние между кодом «до» и «после«). Следовательно, применяются те же правила делегирования, что и для отдельных рекомендаций «до» и «после«, следующим образом.

  • Совет «До» выполняется сначала, прежде чем делегировать или продолжить.

  • Сначала делегируется совет «после» перед выполнением. В частности (и, возможно, несколько нелогично), это означает:

    • Если совет «после» имеет более высокий приоритет, чем совет «вокруг«, то, если совет «вокруг» изменяет какие-либо аргументы метода перед продолжением, на совет «после» также влияет модификация, потому что совет «вокруг» уже перешел к целевому методу с измененными аргументами до того, как сработает совет «после«.

    • Однако тот же самый более высокий приоритет после (возврата) рекомендации не изменяется, если более низкий приоритет вокруг рекомендации изменяет возвращаемое значение, потому что рекомендация «после» (возврата) выполняется перед кодом последующей обработки рекомендации «вокруг» из-за его более высокого приоритета.

    • И наоборот, если совет «после» имеет более низкий приоритет, чем совет «вокруг«, то, если совет «вокруг» изменяет какие-либо аргументы метода перед продолжением, на совет «после» модификация не влияет, поскольку он выполняется вне контекста совета «вокруг«, он не оборачивается им.

    • Однако тот же самый более низкий приоритет после (возврата) рекомендации влияет, если более высокий приоритет вокруг рекомендации изменяет возвращаемое значение, поскольку он выполняется после того, как последний уже закончил выполнение.

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

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

 package de.scrum_master.app;

public class Application {
    public void doSomething(int i) {
        System.out.println("Doing something with "   i);
    }

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

Аспект, вариант 1:

 package de.scrum_master.aspect;

import de.scrum_master.app.Application;

public aspect IntraAspectPrecedence {

    pointcut methodCall(int i) :
        call(void Application.doSomething(int)) amp;amp; args(i);

    void around(int i): methodCall(i) {
        System.out.println("around1 (pre-proceed) -> "   i);
        proceed(11);
        System.out.println("around1 (post-proceed) -> "   i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around2 (pre-proceed) -> "   i);
        proceed(22);
        System.out.println("around2 (post-proceed) -> "   i);
    }

    before(int i): methodCall(i) {
        System.out.println("before1 -> "   i);
    }

    before(int i): methodCall(i) {
        System.out.println("before2 -> "   i);
    }

    after(int i): methodCall(i) {
        System.out.println("after1 -> "   i);
    }

    after(int i): methodCall(i) {
        System.out.println("after2 -> "   i);
    }

}
  

Псевдокод, вариант 1:

 around1(9911) {
    around2(1122) {
        before1(22) {
            before2(22) {
                joinpoint(22) {}
            }
        }
    }
}
after2(99) {
    after1(99) {}
}
  

Вывод на консоль, вариант 1:

Пожалуйста, обратите внимание, что «after1» печатается перед «after2», даже если у него более низкий приоритет, потому что сначала делегируется, затем выполняется, как объяснено выше.

 around1 (pre-proceed) -> 99
around2 (pre-proceed) -> 11
before1 -> 22
before2 -> 22
Doing something with 22
around2 (post-proceed) -> 11
around1 (post-proceed) -> 99
after1 -> 99
after2 -> 99
  

Аспект, вариант 2:

Basically it is the same as before, only the first after advice has the highest precedence.

 package de.scrum_master.aspect;

import de.scrum_master.app.Application;

public aspect IntraAspectPrecedence {

    pointcut methodCall(int i) :
        call(void Application.doSomething(int)) amp;amp; args(i);

    after(int i): methodCall(i) {
        System.out.println("after1 -> "   i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around1 (pre-proceed) -> "   i);
        proceed(11);
        System.out.println("around1 (post-proceed) -> "   i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around2 (pre-proceed) -> "   i);
        proceed(22);
        System.out.println("around2 (post-proceed) -> "   i);
    }

    before(int i): methodCall(i) {
        System.out.println("before1 -> "   i);
    }

    before(int i): methodCall(i) {
        System.out.println("before2 -> "   i);
    }

    after(int i): methodCall(i) {
        System.out.println("after2 -> "   i);
    }

}
  

Псевдокод, вариант 2:

На этот раз, поскольку after1 имеет более высокий приоритет, чем оба совета вокруг, он выполняется сразу после возврата joinpoint и, следовательно, до того, что произойдет после proceed() обертывания советов.

(Примечание для себя: я не доволен этим объяснением, может быть, есть лучший способ сказать это. Возможно, ответ можно было бы перефразировать, работая с одной псевдо-парой советов «до / после» на совет»вокруг» вместо метафоры переноса.)

 around1(9911) {
    around2(1122) {
        before1(22) {
            before2(22) {
                joinpoint(22) {}
            }
        }
        after1(22) {}
    }
}
after2(99) {}
  

Вывод на консоль, вариант 2:

 around1 (pre-proceed) -> 99
around2 (pre-proceed) -> 11
before1 -> 22
before2 -> 22
Doing something with 22
after1 -> 22
around2 (post-proceed) -> 11
around1 (post-proceed) -> 99
after2 -> 99
  

Комментарии:

1. Обновление 2020-10-30: я попытался улучшить объяснение о приоритете рекомендаций «после» и «вокруг», указав на разные случаи с особым упором на то, как измененные аргументы метода и возвращаемые значения из советов вокруг влияют на советы с более высоким или более низким приоритетом.

Ответ №2:

Предшествование рекомендаций по умолчанию, объявленных в том же аспекте, указано в документации следующим образом :

Если две рекомендации определены в одном и том же аспекте, то есть два случая:

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

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

  ---------------
| Around()      |
|   ----------  |
|  | Before() | |
|   ----------  |
|  | Call()   | |
|   ----------  |
 ---------------
| After()       |
 ---------------
  

Значение i изменяется только в пределах Around , поэтому After выводит значение 25 .

Во втором случае порядок дает что-то вроде :

  ---------------
| Before()      |
 ---------------
| Around()      |
|   ----------  |
|  | Call()   | |
|   ----------  |
|  | After()  | |
|   ----------  |
 ---------------
  

В этом случае значения печатаются так, как вы ожидаете.

Комментарии:

1. спасибо за ответ. Не могли бы вы связать меня с ресурсом, который определяет, как происходит переплетение? Я хотел бы понять, почему советы сплетены таким образом.

2. Вы можете прочитать эту статью, в которой более подробно рассказывается о том, как объявлять приоритет между советами в разных аспектах (используя declareprecendence): doanduyhai.wordpress.com/2012/01/01 /…

Ответ №3:

Просто помните, что для рекомендаций после более высокий приоритет означает «выполняется после».

Это все равно отслеживает, что вы пытаетесь сделать с советами после.