当前位置: 首页 > 知识库问答 >
问题:

如何在AspectJ AOP中排除对内部方法的拦截?

孟哲
2023-03-14

有多篇关于如何在Spring AOP中拦截内部方法调用的帖子。但是找不到任何与使用AspectJ排除内部方法相关的帖子。我们希望使用AspectJ编译时编织来实现它promise的运行时性能改进。

如果另一个类的方法调用了下面类TestService中的任何公共方法,则应该拦截该调用。但是,不应该截取从method1()到method2()的内部调用。我们只希望拦截器对每个对象只拦截一次。

public class TestService {
  public void method1() {
    …
    // We do not want the below internal call to be intercepted. 
    this.method2();
  }

  // If some other class's method calls this, intercept the call. But do not intercept the call from method1().
  public void method2() {
    ...     
  }
}

一个示例方面:

@Aspect
public class ServiceAspectJHydrationInterceptor {
    @Pointcut("execution(public * com.companyname.service..impl.*ServiceImpl.*(..))")
    public void serviceLayerPublicMethods() {}

    @Pointcut("@annotation(com.companyname.core.annotation.SkipHydrationInterception)")
    public void skipHydrationInterception() {}

    @Around("serviceLayerPublicMethods() && !skipHydrationInterception()")
    public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
        …
    }
}

共有1个答案

王才英
2023-03-14

您可以使用模式执行(...)&&!CFlowBelow(执行(...))。这对性能不利,因为必须在运行时而不是在编译时检查执行路径(想想callstack),但它做了您想做的事情。由于AspectJ的非代理性质,以及与其他AOP框架相比可用的连接点和切入点更多,例如拦截私有或静态方法,因此要注意一些关键的差异。

下面是一个与您描述的思路一致的小例子:

package de.scrum_master.core.annotation;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface SkipHydrationInterception {}
package de.scrum_master.service.foo.bar.impl;

import de.scrum_master.core.annotation.SkipHydrationInterception;

public class MyServiceImpl {
  public void method1() {
    // We do not want the below internal call to be intercepted.
    method2();
  }

  public void method2() {
    // If some other class's method calls this, intercept the call. But do not
    // intercept the call from method1().
  }

  @SkipHydrationInterception
  public void method3() {
    // Always skip this method one due to the annotation.

    // Should this one be intercepted or not?
    // method1();
  }

  public static void main(String[] args) {
    MyServiceImpl service = new MyServiceImpl();
    service.method1();
    System.out.println("-----");
    service.method2();
    System.out.println("-----");
    service.method3();
  }
}
java prettyprint-override">package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class ServiceAspectJHydrationInterceptor {
  @Pointcut("execution(public !static * de.scrum_master.service..impl.*ServiceImpl.*(..))")
  public void serviceLayerPublicMethods() {}

  @Pointcut("@annotation(de.scrum_master.core.annotation.SkipHydrationInterception)")
  public void skipHydrationInterception() {}

  @Pointcut("serviceLayerPublicMethods() && !skipHydrationInterception()")
  public void interceptMe() {}

  @Around("interceptMe() && !cflowbelow(interceptMe())")
  public Object invoke(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println(pjp);
    return pjp.proceed();
  }
}

现在运行驱动程序应用程序,您将看到以下控制台日志:

execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
-----

但现在取消对method3()主体内部的method1()调用的注释。控制台日志变为:

execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
-----
execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())

问题是:这是你想要的吗?method1()是由一个由于其注释而被排除在拦截之外的方法调用的,但另一方面,它也是一个内部方法调用,我喜欢称之为自调用。解决办法取决于你的答案。

还请注意,从同一类的私有或受保护方法调用的公共方法也将被拦截。因此cflow()cflowbelow()不关心自调用,只关心指定的控制流。

另一种情况:如果一个被拦截的公共方法由于某种原因将调用另一个类,而该类将再次调用第一个类的公共方法,!cflowbelower(...)仍将排除此调用被拦截,因为第一个调用已经在控制流中。

下一种情况:一个公共*ServiceImpl方法调用另一个公共*ServiceImpl方法。结果还会是第二个被调用的方法不会被拦截,因为匹配其执行切入点的东西已经在控制流(调用堆栈)中。

因此,我的解决方案,即使我们调整切入点以覆盖一些角落的情况,本质上与基于代理的解决方案是不一样的。如果在您的环境中可能会发生类似所描述的情况,那么您真的应该重构这些方面,以便进行一些记账(保存状态)和/或使用另一个实例化模型,例如PercFlowBelowle(但我还没有考虑过这个模型,因为我不知道您的确切需求)。但也不是一个讨论论坛,我不能在这里帮助你们。如果你需要更深入的支持,请随时查看我的SO资料中的联系人数据(例如电报),并雇用我。但也许你也可以从这里开始,我只是提一下。

更新:

好的,我想出了一种通过AspectJ模拟基于代理的AOP行为的方法。我不喜欢它,它要求您从execution()切换到call()切入点,也就是说,您不再需要控制(方面编织)被调用方(执行的代码),而是控制调用方(要拦截的方法调用的起源)。

您还需要从if()切入点在两个对象this()target()之间进行运行时检查。我也不喜欢那样做,因为这样会让你的代码变慢,而且在很多地方都要检查。如果与您想要摆脱的基于代理的解决方案相比,您仍然能够达到性能改进的目标,那么您必须自己检查。记住,你现在正在模仿你想要废除的东西,lol。

package de.scrum_master.service.foo.bar.impl;

public class AnotherClass {
  public void doSomething() {
    MyServiceImpl service = new MyServiceImpl();
    service.method1();
    System.out.println("-----");
    service.method2();
    System.out.println("-----");
    service.method3();
    System.out.println("-----");
  }
}
package de.scrum_master.service.foo.bar.impl;

import de.scrum_master.core.annotation.SkipHydrationInterception;

public class MyServiceImpl {
  public void method1() {
    System.out.println("method1");
    method2();
  }

  public void method2() {
    System.out.println("method2");
  }

  @SkipHydrationInterception
  public void method3() {
    System.out.println("method3");
    method1();
  }

  public static void main(String[] args) {
    MyServiceImpl service = new MyServiceImpl();
    service.method1();
    System.out.println("-----");
    service.method2();
    System.out.println("-----");
    service.method3();
    System.out.println("-----");
    new AnotherClass().doSomething();
  }
}

改进后的方面如下所示:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class ServiceAspectJHydrationInterceptor {
  @Pointcut("call(public !static * de.scrum_master.service..impl.*ServiceImpl.*(..))")
  public void serviceLayerPublicMethods() {}

  @Pointcut("@annotation(de.scrum_master.core.annotation.SkipHydrationInterception)")
  public void skipHydrationInterception() {}

  @Pointcut("serviceLayerPublicMethods() && !skipHydrationInterception()")
  public void interceptMe() {}

  @Pointcut("if()")
  public static boolean noSelfInvocation(ProceedingJoinPoint thisJoinPoint) {
    return thisJoinPoint.getThis() != thisJoinPoint.getTarget();
  }

  @Around("interceptMe() && noSelfInvocation(thisJoinPoint)")
  public Object invoke(ProceedingJoinPoint thisJoinPoint, JoinPoint.EnclosingStaticPart thisEnclosingStaticPart) throws Throwable {
    System.out.println(thisJoinPoint);
    System.out.println("  called by: " + thisEnclosingStaticPart);
    return thisJoinPoint.proceed();
  }
}

现在控制台日志如下所示:

call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
  called by: execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.main(String[]))
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
  called by: execution(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.main(String[]))
method2
-----
method3
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method1())
  called by: execution(void de.scrum_master.service.foo.bar.impl.AnotherClass.doSomething())
method1
method2
-----
call(void de.scrum_master.service.foo.bar.impl.MyServiceImpl.method2())
  called by: execution(void de.scrum_master.service.foo.bar.impl.AnotherClass.doSomething())
method2
-----
method3
method1
method2
-----

在我看来,由于Spring AOP或JBoss AOP的代理性质,这正是它们的行为方式。也许我忘了什么,但我想我已经把角落里的案子搞定了。

 类似资料:
  • 在这个问题中,我遇到了与Struts 2相反的问题:仅从defaultStack截取器中排除验证方法 上面的问题涉及到所有的方法都被排除在外,我的问题是没有任何方法被排除在外! 我正在尝试让authenticationInterceptor忽略LoginAction的showLogin方法: 但是,每次我转发到loginInitial时,拦截器都会抓取它,即使我的showLogin方法被排除在外。

  • 问题内容: 主题行基本上说明了一切。我有一个静态方法要拦截,以便可以将周围的建议应用于该方法。我可以使它与任何非静态方法一起使用,但是我不确定如何允许静态方法被拦截。 问题答案: 使用Spring AOP不能做到这一点,因为它是基于代理的。您必须使用AspectJ。看一个简单的例子:http : //blog.jayway.com/2007/02/16/static-mock-using- asp

  • 问题内容: 如何在method内部创建方法?当我创建其显示错误时: 令牌无效@上的语法错误 如果不能在方法内部创建方法,那么请告诉我如何在方法外部创建方法,并从方法中传递方法。 问题答案: *请注意,应使用没有不等号的实际类型(例如“ int”和“ short”)替换此类标记。

  • 本文向大家介绍操作JSON.stringify()方法时,如何不删除对象内部的函数?,包括了操作JSON.stringify()方法时,如何不删除对象内部的函数?的使用技巧和注意事项,需要的朋友参考一下 JSON.stringify()方法不仅对对象进行字符串化 ,而且还删除了在该对象内部发现的所有函数 。因此,要使该函数 不被删除, 应将其转换为字符串,然后仅应使用JSON.stringify(

  • 如何验证是否调用了。我甚至不能设置mock对象,因为一旦我调用该方法,它就会实例化为null并创建新对象。 我尝试使用

  • 我有一个数组像 现在我要移除cartItem对象中的一个产品。我试过这个方法,但是更新后它也把原来的cartitem弄得一塌糊涂。 最后,原始的cartItem对象也没有显示任何东西。 我想要的是从products数组中移除一个产品,并发送回剩余的数组。