您可以使用该模式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 个人资料中的联系数据(例如 Telegram),如果您需要更深入的支持,请雇用我。但也许你也可以从这里拿走,我只是提一下。
更新:
好的,我想出了一种通过 AspectJ 模拟基于代理的 AOP 行为的方法。我不喜欢它,它需要你从切入点切换execution()
,call()
即你不再需要控制(aspect-weave)被调用者(执行代码),而是调用者(被拦截的方法调用的来源)。
您还需要在两个对象之间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 手册。