是否可以排除 AspectJ 中的字段集

Is it possible to exclude field-sets in AspectJ

是否可以排除 AspectJ 切入点中的字段集,以便检测不会在 Java 11 中偶然发现最终字段?

编织以下方面时(完整示例在这里:https://github.com/DaGeRe/aspect-final-example):

@Pointcut("!within(de.aspectjtest..*)")
public void notWithinAspect() {
}

@Pointcut("!set(private * *)")
public void noSet() {
    
}

@Around("notWithinAspect() && noSet()")
public Object aroundStuff(final ProceedingJoinPoint thisJoinPoint, final EnclosingStaticPart thisEnclosingJoinPoint)
        throws Throwable {
    System.out.println("=== Call: " + thisJoinPoint.getSignature() + " " + thisJoinPoint.getKind());
    System.out.println(thisJoinPoint.getSourceLocation() + " " + thisJoinPoint.getStaticPart());
    System.out.println(thisJoinPoint.toLongString());
    return thisJoinPoint.proceed();
}

进入

class FinalFieldConstructorExample {
    private final Integer parameters = 5;
    public Integer getParameters() {
        return parameters;
    }
}

public class MainWithError {
    public static void main(String[] args) {
        FinalFieldConstructorExample example = new FinalFieldConstructorExample();
        System.out.println(example.getParameters());
    }
}

通过 java -cp target/test-0.1-SNAPSHOT.jar -javaagent:../aspect/target/aspectjtest-0.1-SNAPSHOT.jar de.test.MainWithError 我得到

Exception in thread "main" java.lang.IllegalAccessError: Update to non-static final field de.test.FinalFieldConstructorExample.parameters attempted from a different method (init$_aroundBody2) than the initialize
r method <init>                                                                                                                                                                                                    
        at de.test.FinalFieldConstructorExample.init$_aroundBody2(MainWithError.java:5)                                                                                                                            
        at de.test.FinalFieldConstructorExample$AjcClosure3.run(MainWithError.java:1)                                                                                                                              
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                                                                                                                               
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)                                                                                                                                         
        at de.test.FinalFieldConstructorExample.<init>(MainWithError.java:3)                                                                                                                                       
        at de.test.MainWithError.init$_aroundBody2(MainWithError.java:15)                                                                                                                                          
        at de.test.MainWithError$AjcClosure3.run(MainWithError.java:1)                                                                                                                                             
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                     
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)                                                                                                                                         
        at de.test.MainWithError.main_aroundBody10(MainWithError.java)        
        at de.test.MainWithError$AjcClosure11.run(MainWithError.java:1)                                                                                                                                            
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167)                     
        at de.aspectjtest.ExampleAspect.aroundStuff(ExampleAspect.java:27)     
        at de.test.MainWithError.main(MainWithError.java:15)

当我使用 OpenJDK 11 执行它时(将所有内容设置为 Java 8 时,它工作正常)。从 FinalFieldConstructorExample 中删除最终修饰符并从切入点中删除 && noSet() 时,它工作正常并且输出包含

=== Call: Integer java.lang.Integer.valueOf(int) method-call
MainWithoutError.java:5 call(Integer java.lang.Integer.valueOf(int))
call(public static java.lang.Integer java.lang.Integer.valueOf(int))
=== Call: Integer de.test.NonFinalFieldConstructorExample.parameters field-set
MainWithoutError.java:5 set(Integer de.test.NonFinalFieldConstructorExample.parameters)
set(private java.lang.Integer de.test.NonFinalFieldConstructorExample.parameters)

因此,我认为对静态字段的设置调用(具有 field-setgetKind,这似乎不存在于 OpenJDK 8 中)是问题的原因。有什么方法可以将它从 AspectJ 检测中排除(或解决该问题)?文档(https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html#primitive-pointcuts)说get可以在Pointcut中使用,但是我没有找到指定final的方法,即使我添加noSet,它似乎也不知何故触摸并出现错误。

我觉得你在打AspectJ issue #563709。错误消息是相同的,它适用于 Java 8 但不适用于 11(可能是 9+)的事实也是如此。

因此,作为目前的解决方法,您希望避免使用 around-advising 构造函数。要么你通过

排除他们
@Around("notWithinAspect() && noSet() && !(execution(*.new(..)))")

或者,考虑到您的建议仅在 proceed() 之前执行某些操作,只需更改建议类型:

@Before("notWithinAspect() && noSet()")
public void beforeStuff(final JoinPoint thisJoinPoint, final EnclosingStaticPart thisEnclosingJoinPoint) {
  System.out.println("=== Call: " + thisJoinPoint.getSignature() + " " + thisJoinPoint.getKind());
  System.out.println(thisJoinPoint.getSourceLocation() + " " + thisJoinPoint.getStaticPart());
  System.out.println(thisJoinPoint.toLongString());
}

如果出于某种原因您需要 @Around 并且通常无法将其重构为 @Before + @After 对,您可以将其保留在上述排除构造函数执行的情况下并添加一个单独的 @Before + @After 建议对仅用于构造函数。


更新:

Excluding constructors or using only @Before works, but is not usable for my use case (method execution duration monitoring)

好吧,那么这个解决方法怎么样,用 @Before + @After 对全局替换 @Around?您甚至可能注意到您的日志现在还显示了额外的 preinitializationinitialization 切入点,这些切入点以前没有被 around 建议捕获,因为对于那些切入点类型 around 是不支持的。这是我的 MCVE:

package de.scrum_master.app;

public class FinalFieldConstructorExample {
  private final Integer parameters = 5;

  public Integer getParameters() {
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {}
    return parameters;
  }
}
package de.scrum_master.app;

public class MainWithError {
  public static void main(String[] args) {
    FinalFieldConstructorExample example = new FinalFieldConstructorExample();
    System.out.println(example.getParameters());
  }
}
package de.scrum_master.aspect;

import java.util.Stack;

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

@Aspect
public class MyAspect {
  private ThreadLocal<Stack<Long>> startTimeTL = ThreadLocal.withInitial(() -> new Stack<>());

  @Pointcut("within(de.scrum_master.aspect..*)")
  public void withinAspect() {}

  @Before("!withinAspect()")
  public void beforeStuff(final JoinPoint thisJoinPoint) {
    startTimeTL.get().push(System.currentTimeMillis());
  }

  @After("!withinAspect()")
  public void afterStuff(final JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint + " -> " + (System.currentTimeMillis() - startTimeTL.get().pop()));
  }
}

控制台日志如下所示:

staticinitialization(de.scrum_master.app.MainWithError.<clinit>) -> 1
staticinitialization(de.scrum_master.app.FinalFieldConstructorExample.<clinit>) -> 0
preinitialization(de.scrum_master.app.FinalFieldConstructorExample()) -> 0
call(Integer java.lang.Integer.valueOf(int)) -> 0
set(Integer de.scrum_master.app.FinalFieldConstructorExample.parameters) -> 0
execution(de.scrum_master.app.FinalFieldConstructorExample()) -> 1
initialization(de.scrum_master.app.FinalFieldConstructorExample()) -> 1
call(de.scrum_master.app.FinalFieldConstructorExample()) -> 2
get(PrintStream java.lang.System.out) -> 0
call(void java.lang.Thread.sleep(long)) -> 100
get(Integer de.scrum_master.app.FinalFieldConstructorExample.parameters) -> 0
execution(Integer de.scrum_master.app.FinalFieldConstructorExample.getParameters()) -> 100
call(Integer de.scrum_master.app.FinalFieldConstructorExample.getParameters()) -> 100
5
call(void java.io.PrintStream.println(Object)) -> 1
execution(void de.scrum_master.app.MainWithError.main(String[])) -> 103

P.S.: 您是否知道,对于编织方法和构造函数,您正在为相同的 method/constructor 同时记录 callexecution