PHP 中调用和执行切入点的区别? [英] Difference between call and execution pointcuts in PHP?

查看:24
本文介绍了PHP 中调用和执行切入点的区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Java中的AOP(AspectJ)中,当我们谈论方法切入点时,我们可以将它们区分为两个不同的集合:方法调用切入点方法执行切入点.>

基于 SO 上的这些资源:

还有一些AspectJ的背景,我们可以看出,两者之间的差异基本上可以表达如下:

给定这些类:

class CallerObject {//...公共无效一些方法(){CompiletimeTypeObject target = new RuntimeTypeObject();target.someMethodOfTarget();}//...}类 RuntimeTypeObject 扩展 CompileTypeObject {@覆盖公共无效 someMethodOfTarget() {super.someMethodOfTarget();//...一些其他的东西}}类 CompiletimeTypeObject {公共无效 someMethodOfTarget() {//...一些东西}}

  • 方法调用切入点是指从调用者对象调用一个方法,该对象调用目标的方法 对象(实际实现被调用方法的对象).在上面的例子中,调用者是CallerObject,目标是RuntimeTypeObject.此外,方法调用切入点是指对象的编译时类型,即上例中的CompiletimeTypeObject";

所以方法调用切入点是这样的:

切入点方法CallPointcut():调用(无效com.example.CompiletimeTypeObject.someMethodOfTarget())

会匹配CallerObject.someMethod()方法内部的target.someMethodOfTarget();连接点,因为RuntimeTypeObject的compile type是CompiletimeTypeObject,但是这个方法调用切入点:

切入点方法CallPointcut():调用(无效com.example.RuntimeTypeObject.someMethodOfTarget())

不会匹配,因为对象的编译时类型 (CompiletimeTypeObject) 不是 RuntimeTypeObject 或其子类型(相反).

  • 方法执行切入点是指方法的执行(即方法被调用之后之前/em> 方法调用返回).它不提供有关调用者的信息,更重要的是它指的是对象的运行时类型,而不是编译时类型.

因此,这两个方法执行切入点都将匹配 target.someMethodOfTarget(); 执行连接点:

切入点方法CallPointcut():执行(无效com.example.CompiletimeTypeObject.someMethodOfTarget())切入点方法CallPointcut():执行(无效com.example.RuntimeTypeObject.someMethodOfTarget())

因为匹配是基于对象的运行时类型,两者都是 RuntimeTypeObject,而 RuntimeTypeObject 既是 CompiletimeTypeObject(第一个切入点)又是一个 RuntimeTypeObject(第二个切入点).

现在,由于 PHP 不提供对象的编译时类型(除非使用类型提示以某种方式模拟这种行为),在 PHP AOP 实现中区分方法调用和方法执行切入点是否有意义?那么这些切入点将如何不同?

感谢关注!

@kriegaex 指出了 AspectJ 中调用和方法执行切入点之间的另一个有趣方面.

感谢您提供的出色而简洁的示例.我也尝试过自己举个例子,这是我的理解:

情况 A(我使用第 3 方库)中,我实际上无法拦截库方法的执行,因为库本身已经编译成字节码以及与 相关的任何方面该库已经编织到该字节码中(我需要编织源代码才能这样做).

所以我只能拦截对库方法的方法调用,但同样我只能拦截对库方法的调用在我的代码而不是由于相同的原理,从库本身调用库方法(从库本身调用库方法也已经编译).

同样适用于 System 类(相同的原则),就像这里所说的(即使引用指的是 JBoss):

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

<块引用>

不能在执行表达式中使用系统类,因为它不可能对它们进行检测.

如果 B(我为其他用户提供了一个库),如果我确实需要在库本身或将来使用该方法的用户代码中拦截我的库方法的使用,那么我需要使用执行切入点作为切面编织器将编译关注我的库的方法执行和调用切入点,而不是将使用我的库方法的用户代码(只是因为当我编写库时,用户代码尚不存在),因此使用执行切入点将确保编织将发生内部方法执行(有关清晰直观的示例,请查看@kriegaex 伪代码如下),而不是在我的库中调用该方法的任何地方(即在调用方).

因此,当我的库方法在我的库中和用户代码中使用时,我可以拦截该方法的使用(更准确地说,执行).如果我在这种情况下使用了方法调用切入点,我将只拦截从我的库中进行的调用,而不是在用户代码中进行的调用.

无论如何,如果这些考虑因素有意义并且可以应用于 PHP 世界,你们觉得怎么样?

解决方案

免责声明:我不会说 PHP,一点也不会.所以我的回答在本质上相当笼统,而不是特定于 PHP.

AFAIK,PHP 是一种解释型语言,而不是一种编译型语言.所以区别不在于编译时类型与运行时类型,而是语义上声明的类型与实际类型.我想象一个基于 PHP 的 AOP 框架不会编译"任何东西,而是预处理源代码,将额外的(方面)源代码注入到原始文件中.可能仍然可以以某种方式区分声明的类型和实际的类型.

但是还有另一个重要因素也与 callexecution 连接点之间的区别有关:代码编织的位置.想象一下您使用库或自己提供库的情况.每种给定情况的问题是,在应用方面编织时,源代码的哪些部分在用户的控制之下.

  • 案例 A:您使用第 3 方库:让我们假设您不能(或不想)将方面编织到库中.那么你就不能使用execution来拦截库方法,但仍然使用call切入点,因为调用代码在你的控制之下.
  • 案例 B:您向其他用户提供了一个库:让我们假设您的库应该使用方面,但库的用户对此一无所知.然后 execution 切入点将始终有效,因为建议已经融入您的库方法中,无论它们是从外部调用还是从库本身调用.但是 call 只适用于内部调用,因为没有方面代码被编织到用户的调用代码中.

仅当您控制调用以及被调用(执行)的代码时,使用 callexecution 并没有太大区别.但是等一下,它还是有区别的:execution 只是编织在一个地方,而 call 它可能编织到许多地方,因此生成的代码量较小执行.

<小时>

更新:

这是一些伪代码,应要求提供:

让我们假设我们有一个类 MyClass 是要进​​行方面增强的(通过源代码插入):

class MyClass {方法 foo() {打印(富");酒吧();}方法栏(){打印(酒吧");佐特();}方法 zot() {打印(佐特");}静态方法 main() {new McClass().foo();}}

现在,如果我们使用 call()

像这样应用 CallAspect

aspect CallAspect {之前():调用(* *(..)){打印(之前" + thisJoinPoint);}}

在我们的代码上,源代码编织后看起来像这样:

class MyClass {方法 foo() {打印(富");打印(呼叫前(MyClass.bar())");酒吧();}方法栏(){打印(酒吧");打印(呼叫前(MyClass.zot())");佐特();}方法 zot() {打印(佐特");}静态方法 main() {打印(呼叫前(MyClass.foo())");new McClass().foo();}}

或者,如果我们使用 execution()

应用这样的 ExecutionAspect

aspect ExecutionAspect {之前():执行(* *(..)){打印(之前" + thisJoinPoint);}}

在我们的代码上,源代码编织后看起来像这样:

class MyClass {方法 foo() {print("执行前(MyClass.foo())");打印(富");酒吧();}方法栏(){print("执行前(MyClass.bar())");打印(酒吧");佐特();}方法 zot() {print("执行前(MyClass.zot())");打印(佐特");}静态方法 main() {print("执行前(MyClass.main())");new McClass().foo();}}

你现在能看出区别了吗?注意代码的位置以及什么打印语句.

In AOP in Java (AspectJ) when we talk about method pointcuts, we can differentiate them into two different sets: method call pointcuts and method execution pointcuts.

Basing on these resources here on SO:

And some AspectJ background, we can tell that basically the differences between the two can be expressed as the following:

Given these classes:

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
    }
}

  • A method call pointcut refers to the call of a method from a caller object which calls the method of a target object (the one which actually implements the method being called). In the example above, the caller is CallerObject, the target is RuntimeTypeObject. Also, a method call pointcut refers to the compile time type of the object, i.e. "CompiletimeTypeObject" in the example above;

So a method call pointcut like this:

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

Will match the target.someMethodOfTarget(); join point inside the CallerObject.someMethod() method as the compile type of the RuntimeTypeObject is CompiletimeTypeObject, but this method call pointcut:

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

Will not match, as the compile time type of the object (CompiletimeTypeObject) is not a RuntimeTypeObject or a subtype of it (it is the opposite).

  • A method execution pointcut refers to the execution of a method (i.e. after the method has been called or right before the method call returns). It doesn't give information about the caller and more important it refers to the runtime type of the object and not to the compile time type.

So, both these method execution pointcuts will match the target.someMethodOfTarget(); execution join point:

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

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

As the matching is based on the runtime type of the object which is RuntimeTypeObject for both and RuntimeTypeObject is both CompiletimeTypeObject (first pointcut) and a RuntimeTypeObject (second pointcut).

Now, as PHP doesn't provide compile time types for objects (unless type-hinting is used to somehow emulate this behaviour), does it make sense to differentiate method call and method execution pointcuts in a PHP AOP implementation? How then will the pointcuts differ from each other?

Thanks for the attention!

EDIT: @kriegaex has pointed out another interesting aspect between call and method execution pointcuts in AspectJ.

Thank you for the great and concise example. I have tried to make an example myself too and here is what I understood:

In case A (I use a 3rd party library), I actually can't intercept the execution of a library method because the library itself was already compiled into bytecode and any aspect concerning that library was already woven into that bytecode too (I would need to weave the sources in order to do so).

So I can only intercept the method calls to the library methods, but again I can only intercept the calls to library methods in my code and not the calls to library methods from within the library itself because of the same principle (the calls to library methods from within the library itself are also already compiled).

The same applies for System classes (same principle) as is said here (even if the reference refers to JBoss):

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

System classes cannot be used within execution expressions because it is impossible to instrument them.

In case B (I provide a library for other users), if I actually need to intercept the usage of a method of my library either in the library itself or in the future user code which will use that method, then I need to use an execution pointcut as the aspect weaver will compile both the method execution and call pointcuts that concern my library and not the user code which will use my library methods (simply because the user code doesn't exist yet when I am writing the library), therefore using an execution pointcut will ensure that the weaving will occur inside the method execution (for a clear and intuitive example, look at the @kriegaex pseudo-code below) and not wherever the method is called within my library (i.e. at the caller side).

So I can intercept the usage (more precisely, execution) of my library method both when the method is used within my library and in the user's code. If I had used a method call pointcut in this case, I would have intercepted only the calls made from within my library, and not the calls made in the user's code.

Anyway, still think if these considerations make sense and can be applied in the PHP world, what do you think guys?

解决方案

Disclaimer: I do not speak PHP, not even a little. So my answer is rather general in nature than specific to PHP.

AFAIK, PHP is an interpreted rather than a compiled language. So the difference is not compile time vs. runtime type, but semantically rather declared vs. actual type. I imagine that a PHP-based AOP framework would not "compile" anything but rather preprocess source code, injecting extra (aspect) source code into the original files. Probably it would still be possible to differentiate declared from actual types somehow.

But there is another important factor which is also relevant to the difference between call vs execution joinpoints: The place in which the code is woven. Imagine situations in which you use libraries or provide them by yourself. The question for each given situation is which parts of the source code is under the user's control when applying aspect weaving.

  • Case A: You use a 3rd party library: Let us assume you cannot (or do not want to) weave aspects into the library. Then you cannot use execution for intercepting library methods, but still use call pointcuts because the calling code is under your control.
  • Case B: You provide a library to other users: Let us assume your library should use aspects, but the library's user does not know anything about it. Then execution pointcuts will always work because the advices are already woven into your library's methods, no matter if they are called from outside or from the library itself. But call would only work for internal calls because no aspect code was woven into the user's calling code.

Only if you control the calling as well as the called (executed) code it does not make so much difference whether you use call or execution. But wait a minute, it still makes a difference: execution is just woven in one place while call it woven into potentially many places, so the amount of code generated is smaller for execution.


Update:

Here is some pseudo code, as requested:

Let us assume we have a class MyClass which is to be aspect-enhanced (via source code insertion):

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

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

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

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

Now if we apply a CallAspect like this using call()

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

upon our code, it would look like this after source code weaving:

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();
    }
}

Alternatively, if we apply an ExecutionAspect like this using execution()

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

upon our code, it would look like this after source code weaving:

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();
    }
}

Can you see the difference now? Pay attention to where the code is woven into and what the print statements say.

这篇关于PHP 中调用和执行切入点的区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆