我们正在通过编译时编织将使用Jboss AOP(基于代理)的应用程序迁移到AspectJ AOP。 但是,我们不希望内部方法被AspectJ拦截,但这似乎是AspectJ的默认行为。
关于如何在Spring AOP中拦截内部方法调用的文章很多。但是,找不到与使用AspectJ排除内部方法有关的任何帖子。我们希望使用AspectJ编译时织法来实现它所承诺的运行时性能改进。
如果另一个类的方法调用下面的类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 {
…
}
}
Spring AOP中默认排除内部方法调用拦截的行为,因为它是基于代理的。有没有一种方法可以使用AspectJ和编译时编织来排除内部方法的拦截?
软件详细信息: 春季版:3.2.14。 JDK版本:1.8。 Maven插件Codehaus“ aspectj-maven-plugin” 1.7版用于进行编译时间编织。
答案 0 :(得分:0)
您可以使用模式execution(...) && !cflowbelow(execution(...))
。这对性能不好,因为必须在运行时而不是编译时检查执行路径(认为是调用堆栈),但是它可以执行所需的操作。提防由于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();
}
}
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())
-----
这正是您想要的。到目前为止,一切都很好。另请注意执行切入点中的!static
限定符,否则将static main(..)
截获。
但是现在取消注释method1()
内的method3()
调用。控制台日志变为:
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()
不在乎自调用,而只在乎指定的控制流。
另一种情况:如果被拦截的公共方法由于某种原因将调用另一个类,并且该类将再次调用第一类的公共方法,则!cflowbelow(...)
仍将排除此调用,因为第一个调用是已经在控制流中。
下一种情况:一种公共*ServiceImpl
方法调用另一种公共*ServiceImpl
方法。结果还可能是第二个被调用方法不会被拦截,因为与它的执行切入点匹配的对象已经在控制流(调用堆栈)中。
因此,即使我们调整切入点以涵盖一些极端情况,我的解决方案也与基于代理的解决方案本质上并不相同。如果在您的环境中可能发生上述情况那样的极端情况,那么您确实应该重构这些方面,以便进行一些记账(保存状态)和/或使用诸如percflowbelow
之类的其他实例化模型(但没有以为通过,因为我不知道您的确切要求。但是,SO不是讨论论坛,在这里我无法为您提供更多帮助。请随时在我的SO个人资料中查看联系数据(例如电报),如果需要更多深入的支持,请雇用我。但是也许您也可以从这里拿走,我只是在提到它。
更新:
好的,我想出了一种通过AspectJ模拟基于代理的AOP行为的方法。我不喜欢它,它要求您从execution()
切换到call()
切入点,即您不再需要控制(纵横交织)被调用者(执行的代码),而是控制调用者(执行代码的来源)方法调用被拦截)。</ p>
您还需要从this()
切入点在两个对象target()
和if()
之间进行运行时检查。我也不喜欢,因为它会使您的代码变慢,并且必须在许多地方进行检查。与您想要摆脱的基于代理的解决方案相比,如果您仍然能够达到提高性能的目标,则必须自己进行检查。记住,您现在正在模仿要取消的内容,哈哈。
让我们添加另一个类,以模拟外部类调用目标类的相互作用,而不仅仅是从静态方法中调用目标类,这是不够的测试用例。
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("-----");
}
}
最初的MyServiceImpl
类通过记录更多内容并调用AnotherClass.doSomething()
进行了扩展。
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由于其代理性质而将表现的方式。也许我忘记了一些东西,但我想我几乎把角落的箱子都盖了。
如果您在理解此解决方案时遇到问题,请告诉我。至于我使用的切入点指示符的含义,请查阅AspectJ手册。