由于无限循环导致的 AspectJ stackoverflowerror

AspectJ stackoverflowerror due to infinite loop

我正在检测第 3 方应用程序 - 并定义了以下切入点

@Aspect
public class MyAspect {

    @Pointcut("execution(some.app.Application.new(..))")
    public void appCreation() {
    }

    @After("appCreation()")
    public void afterCreation() {
        MyUtil.doSomething();
    }  
}

现在的问题是 MyUtil.doSomething() 最终调用了 some.app.Application 的构造函数 - 这当然是 "detected" 在我看来 MyUtil.doSomething() 被调用再次调用......你明白了。

我试图在 Pointcut 定义中加入 && !within(MyAspect) 但它没有帮助。在 MyUtil 位于调用堆栈的更上方的情况下,是否有任何方法可以抑制切入点的检测?

如果相关:MyUtil.doSomething() 不是直接调用应用程序构造函数,而是在几次中间调用之后调用

如果您找不到更优雅的方式,您可以随时使用 ThreadLocal:

@Aspect
public class MyAspect {
    private static final ThreadLocal<Boolean> callInProgress = new ThreadLocal<Boolean>() {
        @Override protected Boolean initialValue() {
           return false;
        }
    };

    @Pointcut("execution(some.app.Application.new(..))")
    public void appCreation() {
    }

    @After("appCreation()")
    public void afterCreation() {
        if (!callInProgress.get()) {
           callInProgress.set(true);
           MyUtil.doSomething();
           callInProgress.set(false);
        }
    }  
}

好的,首先让我们重现您的问题:

Java 类:

package de.scrum_master.app;

public class Application {
    public Application() {
        System.out.println("Creating application");
    }

    public static void main(String[] args) {
        new Application();
    }
}
package de.scrum_master.app;

public class MyUtil {
    public static void doSomething() {
        System.out.println("Doing something");
        new Application();
    }
}

导致递归的问题方面:

package de.scrum_master.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import de.scrum_master.app.MyUtil;

@Aspect
public class MyAspect {
    @Pointcut("execution(*..Application.new(..))")
    public void appCreation() {}

    @After("appCreation()")
    public void afterCreation() {
        MyUtil.doSomething();
    }
}

控制台日志:

Creating application
Doing something
Creating application
Doing something
(...)
Exception in thread "main" java.lang.WhosebugError
    at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
    at java.nio.charset.CharsetEncoder.encode(Unknown Source)
    at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
    at sun.nio.cs.StreamEncoder.write(Unknown Source)
    at java.io.OutputStreamWriter.write(Unknown Source)
    at java.io.BufferedWriter.flushBuffer(Unknown Source)
    at java.io.PrintStream.write(Unknown Source)
    at java.io.PrintStream.print(Unknown Source)
    at java.io.PrintStream.println(Unknown Source)
    at de.scrum_master.app.MyUtil.doSomething(MyUtil.java:5)
    at de.scrum_master.aspect.MyAspect.afterCreation(MyAspect.aj:16)
    at de.scrum_master.app.Application.<init>(Application.java:6)
    at de.scrum_master.app.MyUtil.doSomething(MyUtil.java:6)
    (...)

现在我们如何避免这个问题呢?我们需要避免执行已经在执行的建议,即如果它在当前 控制流 cflow() 中。对于建议执行,甚至还有一个特殊的切入点 adviceexecution().

避免无限递归的改进方面:

package de.scrum_master.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import de.scrum_master.app.MyUtil;

@Aspect
public class MyAspect {
    @Pointcut("adviceexecution() && within(MyAspect)")
    public void myAdvice() {}

    @Pointcut("execution(*..Application.new(..))")
    public void appCreation() {}

    @After("appCreation() && !cflow(myAdvice())")
    public void afterCreation() {
        MyUtil.doSomething();
    }
}

更正后的控制台日志:

Creating application
Doing something
Creating application

最后一点:到目前为止,我并没有质疑您的应用程序逻辑。现在我是:如果已经创建了一个应用程序,那么从实用程序方法中创建另一个应用程序真的有意义吗?我想尽管这对我来说是一个有趣的 AOP 练习,但真正的问题是在 Java 代码中,而不是在 AspectJ 代码中。