PHP中的调用和执行切入点之间的区别?

时间:2015-01-31 12:30:03

标签: java php aop aspectj

在Java中的AOP(AspectJ)中,当我们讨论方法切入点时,我们可以将它们区分为两个不同的集合:method call pointcutsmethod 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
    }
}
  • 方法调用切入点是指来自调用者对象的方法的调用,该对象调用目标<的方法/ em> object(实际实现被调用方法的对象)。在上面的示例中,调用者是 CallerObject ,目标是 RuntimeTypeObject 。此外,方法调用切入点是指对象的编译时类型,即&#34; CompiletimeTypeObject&#34;在上面的例子中;

所以这样method call pointcut

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

将匹配CallerObject.someMethod()方法中的target.someMethodOfTarget();连接点,因为RuntimeTypeObject的compile type是CompiletimeTypeObject,但此方法调用切入点:

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

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

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

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

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

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

由于匹配基于对象的运行时类型,对于两者都是RuntimeTypeObject,RuntimeTypeObject同时是CompiletimeTypeObject(第一个切入点)和RuntimeTypeObject(第二个切入点)。

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

感谢您的关注!

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

感谢您提供精彩而简洁的示例。我也试图自己做一个例子,这就是我所理解的:

案例A(我使用的是第三方库)中,我实际上无法拦截库方法的执行,因为库本身已经编译成字节码和任何有关的方面< em>那个库 已经编织到那个字节码中(我需要编织源代码才能这样做)。

所以我只能拦截对库方法的方法调用,但我再次只能拦截调用到我的代码中的库方法而不是从库本身调用库方法,因为原理相同(库本身对库方法的调用也已编译)。

同样适用于此处所述的系统类(相同原则)(即使引用引用JBoss):

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

  

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

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

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

无论如何,仍然认为如果这些考虑因素有意义且可以在PHP世界中应用,你认为那些人呢?

2 个答案:

答案 0 :(得分:3)

免责声明: 我不会说PHP,甚至不会说一点。所以我的答案相当普遍,而不是特定于PHP。

AFAIK,PHP是一种解释而非编译语言。因此,差异不是编译时间与运行时类型,而是语义而不是声明与实际类型。我认为基于PHP的AOP框架不会“编译”任何东西,而是预处理源代码,将额外(方面)源代码注入原始文件。可能仍然可以以某种方式区分声明的类型和实际的类型。

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

  • 案例A:您使用第三方库:让我们假设您不能(或不想)将方面编织到库中。然后,您无法使用execution拦截库方法,但仍然使用call切入点,因为调用代码在您的控制之下。
  • 案例B:您向其他用户提供了一个库:让我们假设您的库应该使用方面,但库的用户对此一无所知。然后execution切入点将始终有效,因为建议已经编入您的库的方法中,无论它们是从外部调用还是从库本身调用。但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();
    }
}

你现在能看出差异吗?请注意代码编组的 和打印语句

答案 1 :(得分:1)

PHP是动态语言,因此实现call个连接点非常困难,因为有许多语言功能,如call_user_func_array()$func = 'var_dump'; $func($func);

@kriegaex写了一个很好的答案,其中callexecution类型的连接点之间存在主要差异。应用于PHP,现在只有可能的连接点是一个execution连接点,因为通过使用装饰器包装类或为此提供PHP扩展来挂钩方法函数的执行要容易得多。

实际上,Go! AOP framework仅提供execution个连接点,以及FLOW3框架等。