具有覆盖接口方法的特定注释的方法的切入点

Pointcut for methods with a specific annotation that override methods of an interface

请考虑以下设置:

public interface IVehicle {
    public void start() {}  
    public void move() {}  
    public void stop() {}
}
public class RaceCar implements IVehicle {
  @Override
  @Authenticated(Role.Pilot)
  public void start() { /* Logic to start the car. */ }  
  @Override
  @Authenticated(Role.Pilot)
  public void move() { /* Logic to move the car. */ }  
  @Override
  @Authenticated(Role.Pilot)
  public void stop() { /* Logic to the stop car. */ }
}
public class RacingApp {
    public static void main(String[] args) {
        IVehicle raceCar = new RaceCar();
        raceCar.start();
        raceCar.move();
        raceCar.stop();        
    }  
} 

我需要在 RacingApp class 中获取对 RaceCar 方法的所有调用,这些方法具有 @Authenticated 注释。问题是调用是对接口 IVehicle 而不是对 class RaceCar 本身进行的。通过多态性,方法被推断为来自 RaceCar class 在 运行 时间。

我尝试了很多切入点,但我还没有能够实现这一点。在我看来,到目前为止我最好的切入点如下:

@Pointcut("call(@Authenticated * IVehicle+.*(..)) && within(RacingApp)")

我认为我已经很接近了,但我似乎无法让它发挥作用。有谁知道这是如何实现的?

首先,稍微纠正一下:在 IVehicle 中,方法在声明后不能有主体,只能有分号,每个方法的 public 是多余的,因为接口中声明的所有方法都是 public 根据定义。所以它应该看起来像这样至少可以编译:

package de.scrum_master.app;

public interface IVehicle {
  void start();
  void move();
  void stop();
}

为了让您的示例为我编译,我还像这样重新创建了另一个助手 classes,以便获得 minimal, complete, and verifiable example(我认为这应该是您的工作,顺便说一句) :

package de.scrum_master.app;

public enum Role {
  Pilot, Passenger
}
package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {
  Role value();
}
package de.scrum_master.app;

public class RaceCar implements IVehicle {
  @Override
  @Authenticated(Role.Pilot)
  public void start() {
    System.out.println("Starting");
  }

  @Override
  @Authenticated(Role.Pilot)
  public void move() {
    System.out.println("Moving");
  }

  @Override
  @Authenticated(Role.Pilot)
  public void stop() {
    System.out.println("Stopping");
  }
}
package de.scrum_master.app;

public class RacingApp {
  public static void main(String[] args) {
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
  }
}

关于你的方面,有一个细节让它无法正常工作:当拦截一个 call() 到接口方法时,JVM 和 AspectJ 只知道接口没有你正在过滤的注释.由于多态性,正如您已经提到的,只有在 execution() 期间才能清楚执行 class 方法的具体实现。

另请注意,如果您不使用本机 AspectJ 语法,而是使用繁琐的注释驱动的 @AspectJ 样式,则需要使用完全限定的 class 名称(即包括包),以便使您的切入点匹配。例如:

package de.scrum_master.aspect;

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

@Aspect
public class VehicleActionInterceptor {
  @Before("execution(@de.scrum_master.app.Authenticated * de.scrum_master.app.IVehicle+.*(..))")
  public void beforeAction(JoinPoint thisJoinPoint) {
    System.out.println(thisJoinPoint);
  }
}

当 运行 驱动程序应用程序时,这会产生以下控制台输出:

execution(void de.scrum_master.app.RaceCar.start())
Starting
execution(void de.scrum_master.app.RaceCar.move())
Moving
execution(void de.scrum_master.app.RaceCar.stop())
Stopping

如果你想对注解做些什么,你也可以将它绑定到一个参数:

package de.scrum_master.aspect;

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

import de.scrum_master.app.Authenticated;

@Aspect
public class VehicleActionInterceptor {
  @Before("execution(* de.scrum_master.app.IVehicle+.*(..)) && @annotation(authenticated)")
  public void beforeAction(JoinPoint thisJoinPoint, Authenticated authenticated) {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

现在输出变成:

execution(void de.scrum_master.app.RaceCar.start()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Starting
execution(void de.scrum_master.app.RaceCar.move()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Moving
execution(void de.scrum_master.app.RaceCar.stop()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Stopping

同一方面采用更优雅的本机语法,无需使用完全限定的 class 名称,因为您可以使用导入:

package de.scrum_master.aspect;

import de.scrum_master.app.IVehicle;
import de.scrum_master.app.Authenticated;

public aspect VehicleActionInterceptor {
  before(Authenticated authenticated) : execution(* IVehicle+.*(..)) && @annotation(authenticated) {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

更新: OP 询问如何将连接点匹配限制为仅由某个 class 调用产生的那些执行。解决方案是将 execution() 切入点与像 cflow(call(...) && within(ClassOfInterest)).

这样的控制 clow 切入点结合起来

但是首先让我们用更多的日志输出和第二个应用程序扩展测试用例 class 因为我们需要一个否定的测试用例:

package de.scrum_master.app;

public class RacingApp {
  public static void main(String[] args) {
    System.out.println("=== Racing app ===");
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
    AnotherApp.main(args);
  }
}
package de.scrum_master.app;

public class AnotherApp {
  public static void main(String[] args) {
    System.out.println("=== Another app ===");
    IVehicle raceCar = new RaceCar();
    raceCar.start();
    raceCar.move();
    raceCar.stop();
  }
}

现在我们扩展方面:

package de.scrum_master.aspect;

import de.scrum_master.app.IVehicle;
import de.scrum_master.app.Authenticated;
import de.scrum_master.app.RacingApp;

public aspect VehicleActionInterceptor {
  before(Authenticated authenticated) :
    execution(* IVehicle+.*(..)) && @annotation(authenticated) &&
    cflow(call(* IVehicle+.*(..)) && within(RacingApp))
  {
    System.out.println(thisJoinPoint + " -> " + authenticated);
  }
}

日志输出现在变成:

=== Racing app ===
execution(void de.scrum_master.app.RaceCar.start()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Starting
execution(void de.scrum_master.app.RaceCar.move()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Moving
execution(void de.scrum_master.app.RaceCar.stop()) -> @de.scrum_master.app.Authenticated(value=Pilot)
Stopping
=== Another app ===
Starting
Moving
Stopping

瞧瞧!