Разница между вызовом и выполнения pointcut в PHP?

В AOP в Java (AspectJ), когда мы говорим о точечных вызовах методов, мы можем разделить их на два разных набора: method call pointcuts а также method execution pointcuts,

Основываясь на этих ресурсах здесь, на SO:

И немного фона AspectJ, мы можем сказать, что в основном различия между ними могут быть выражены следующим образом:

Учитывая эти классы:

class CallerObject {
//...
public void someMethod() {
CompiletimeTypeObject target = new RuntimeTypeObject();
target.someMethodOfTarget();
}
//...
}

class RuntimeTypeObject extends CompileTypeObject {
@Override
public void someMethodOfTarget() {
super.someMethodOfTarget();
//...some other stuff
}
}

class CompiletimeTypeObject {
public void someMethodOfTarget() {
//...some stuff
}
}
  • вызов метода pointcut относится к вызов метода из гость объект, который вызывает метод цель объект (тот, который фактически реализует вызываемый метод). В приведенном выше примере вызывающий CallerObject, цель RuntimeTypeObject. Кроме того, pointcut вызова метода ссылается на тип времени компиляции объекта, то есть «CompiletimeTypeObject» в приведенном выше примере;

Так что method call pointcut как это:

pointcut methodCallPointcut():
call(void com.example.CompiletimeTypeObject.someMethodOfTarget())

Будет соответствовать target.someMethodOfTarget(); точка соединения внутри метода CallerObject.someMethod () в качестве compile type RuntimeTypeObject является CompiletimeTypeObject, но этот метод вызывает pointcut:

pointcut methodCallPointcut():
call(void com.example.RuntimeTypeObject.someMethodOfTarget())

Не будет совпадать, так как тип времени компиляции объекта (CompiletimeTypeObject) не является RuntimeTypeObject или его подтипом (он противоположен).

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

Итак, обе эти точки выполнения метода будут соответствовать target.someMethodOfTarget(); точка соединения исполнения:

pointcut methodCallPointcut():
execution(void com.example.CompiletimeTypeObject.someMethodOfTarget())

pointcut methodCallPointcut():
execution(void com.example.RuntimeTypeObject.someMethodOfTarget())

Поскольку сопоставление основано на типе времени выполнения объекта, который для обоих типов RuntimeTypeObject и RuntimeTypeObject является одновременно CompiletimeTypeObject (первый pointcut) и RuntimeTypeObject (второй pointcut).

Теперь, так как PHP не предоставляет типы времени компиляции для объектов (если только подсказка типа не используется для эмуляции этого поведения), имеет ли смысл различать точки вызова вызова метода и выполнения метода в реализации PHP AOP? Как тогда будут отличаться pointcuts друг от друга?

Спасибо за внимание!

РЕДАКТИРОВАТЬ: @kriegaex указал еще один интересный аспект между вызовами и вызовами выполнения метода в AspectJ.

Спасибо за отличный и лаконичный пример. Я тоже пытался сделать пример, и вот что я понял:

В вариант A (я использую стороннюю библиотеку), Я на самом деле не могу перехватить выполнение метода библиотеки, потому что сама библиотека уже была скомпилирована в байт-код и любой аспект, касающийся эта библиотека было уже сплетенный в этот байт-код тоже (мне нужно было бы сплести источники, чтобы сделать это).

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

То же самое относится к системным классам (тот же принцип), как сказано здесь (даже если ссылка относится к JBoss):

https://docs.jboss.org/jbossaop/docs/2.0.0.GA/docs/aspect-framework/reference/en/html/pointcuts.html

Системные классы нельзя использовать в выражениях выполнения, потому что
невозможно их инструментировать.

В случае B (я предоставляю библиотеку для других пользователей), если мне действительно нужно перехватить использование метода моей библиотеки либо в самой библиотеке, либо в будущем пользовательском коде, который будет использовать этот метод, тогда мне нужно использовать выполнение pointcut в качестве ткача аспекта скомпилирует выполнение метода и вызовы pointcut беспокойство моя библиотека а не пользовательский код, который будет использовать мои методы библиотеки (просто потому, что пользовательский код еще не существует, когда я пишу библиотеку), поэтому использование выполнения pointcut гарантирует, что будет происходить переплетение внутри выполнение метода (для ясного и понятного примера посмотрите на псевдокод @kriegaex ниже), а не везде, где метод вызывается в моей библиотеке (то есть на стороне вызывающей стороны).

Поэтому я могу перехватить использование (точнее, выполнение) моего библиотечного метода, когда метод используется в моей библиотеке и в коде пользователя.
Если бы в этом случае я использовал pointcut вызова метода, я бы перехватил только выполненные вызовы изнутри моя библиотека, а не звонки, сделанные в коде пользователя.

В любом случае, подумайте, если эти соображения имеют смысл и могут быть применены в мире PHP, как вы думаете, ребята?

1

Решение

Отказ от ответственности: Я не говорю на PHP, даже немного. Поэтому мой ответ носит скорее общий характер, чем специфический для PHP.

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

Но есть еще один важный фактор, который также имеет отношение к разнице между call против execution точки соединения: место, в котором сплетен код. Представьте себе ситуации, в которых вы используете библиотеки или сами предоставляете их. Вопрос для каждой данной ситуации состоит в том, какие части исходного кода находятся под контролем пользователя при применении аспектного плетения.

  • Случай A: Вы используете стороннюю библиотеку: Допустим, вы не можете (или не хотите) вплетать аспекты в библиотеку. Тогда вы не можете использовать execution для перехвата библиотечных методов, но все еще используют call pointcuts, потому что вызывающий код находится под вашим контролем.
  • Случай B: Вы предоставляете библиотеку другим пользователям: Давайте предположим, что ваша библиотека должна использовать аспекты, но пользователь библиотеки ничего об этом не знает. затем execution pointcuts всегда будут работать, потому что советы уже вплетены в методы вашей библиотеки, независимо от того, вызываются они извне или из самой библиотеки. Но call будет работать только для внутренних вызовов, потому что в код вызова пользователя не вплетен код аспекта.

Только если вы управляете как вызывающим, так и вызываемым (исполняемым) кодом, это не имеет большого значения, используете ли вы call или же execution, Но подождите минуту, это все еще имеет значение: execution просто сплетен в одном месте, а call он сплетен в потенциально много мест, поэтому объем генерируемого кода меньше для execution,


Обновить:

Вот некоторый псевдокод, как и было запрошено:

Допустим, у нас есть класс MyClass который должен быть расширен (через вставку исходного кода):

class MyClass {
method foo() {
print("foo");
bar();
}

method bar() {
print("bar");
zot();
}

method zot() {
print("zot");
}

static method main() {
new McClass().foo();
}
}

Теперь, если мы применим CallAspect как это с помощью call()

aspect CallAspect {
before() : call(* *(..)) {
print("before " + thisJoinPoint);
}
}

в нашем коде это будет выглядеть после переплетения исходного кода:

class MyClass {
method foo() {
print("foo");
print("before call(MyClass.bar())");
bar();
}

method bar() {
print("bar");
print("before call(MyClass.zot())");
zot();
}

method zot() {
print("zot");
}

static method main() {
print("before call(MyClass.foo())");
new McClass().foo();
}
}

В качестве альтернативы, если мы применим ExecutionAspect как это с помощью execution()

aspect ExecutionAspect {
before() : execution(* *(..)) {
print("before " + thisJoinPoint);
}
}

в нашем коде это будет выглядеть после переплетения исходного кода:

class MyClass {
method foo() {
print("before execution(MyClass.foo())");
print("foo");
bar();
}

method bar() {
print("before execution(MyClass.bar())");
print("bar");
zot();
}

method zot() {
print("before execution(MyClass.zot())");
print("zot");
}

static method main() {
print("before execution(MyClass.main())");
new McClass().foo();
}
}

Теперь ты видишь разницу? Обрати внимание на где код вплетен и какие в печатных заявлениях говорится.

3

Другие решения

PHP динамический язык, поэтому его довольно сложно реализовать call точки соединения, потому что есть много языковых функций, таких как call_user_func_array(), $func = 'var_dump'; $func($func);

@kriegaex написал хороший ответ с основными отличиями между call а также execution типы точек соединения. Применительно к PHP, единственной возможной точкой соединения на данный момент является execution joinpoint, потому что намного проще перехватить выполнение метода | методом, обернув класс декоратором или предоставив для этого расширение PHP.

На самом деле, Идти! AOP Framework обеспечивает только execution joinpoint, а также фреймворк FLOW3 и другие.

1

По вопросам рекламы [email protected]