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

标签 java php aop aspectj

在Java AOP(AspectJ)中,当我们谈论方法切入点时,我们可以将它们分为两个不同的集合:method call pointcutsmethod execution pointcuts

基于SO的这些资源:


execution Vs. call Join point
Difference between call and execution in AOP


在某些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
    }
}



方法调用切入点是指从调用方对象调用方法,该调用方对象调用目标对象的方法(实际上是实现所调用方法的对象)。在上面的示例中,调用者是CallerObject,目标是RuntimeTypeObject。另外,方法调用切入点是指对象的编译时间类型,即上述示例中的“ CompiletimeTypeObject”;


因此,这样的method call pointcut

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


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

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


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


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


因此,这两个方法执行切入点都将匹配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(我使用第3方库),我实际上无法截获库方法的执行,因为库本身已经被编译成字节码,并且与该库有关的任何方面也已经被编织到该字节码中(我需要编织源以做到这一点)。

因此,我只能拦截对库方法的方法调用,但是由于相同的原理(对库方法的调用),我只能拦截代码中对库方法的调用,而不能拦截库本身内部对库方法的调用。从库本身内部也已经编译)。

同样适用于系统类(相同的原理),如此处所述(即使引用引用的是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却编织在许多地方,所以对于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();
    }
}


现在可以看到区别了吗?注意代码的编写位置以及打印语句的内容。

关于java - PHP中调用和执行切入点之间的区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28251596/

相关文章:

java - 如何通过在 Java 中检索异常方法来获取注解?

java - 自然语言处理

java - 试图弄清楚如何最小化 ehcache 日志(n.s.e.constructs.web.filter.Filter)

php - 如何在 Kohana 3 项目中安排业务逻辑

php - MySQL查询从多个表中选择,显示全部来自table1 +来自table2的一些数据

java - AspectJ 切入点到方法调用(即使它是在外部库上调用的)

java - 使用来自 Excelsheet 的相同值执行多个测试。使用@DataProvider

php - AzurePHP - 轮询 Azure 队列

java - Spring AOP - 拦截其祖先有注释的类

java - 编织一个没有接口(interface)和非公共(public)方法的类时关于理解Spring AOP的问题