java - 具有覆盖接口(interface)方法的特定注释的方法的切入点

标签 java aop aspectj aspect

请考虑以下设置:

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();        
    }  
} 

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

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

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

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

最佳答案

首先,有一点更正:在 IVehicle 中,方法不能有主体,但在声明后有分号,并且每个方法的 public 是多余的,因为在根据定义,接口(interface)是公共(public)的。所以它应该看起来像这样至少可以编译:

package de.scrum_master.app;

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

为了让你的示例为我编译,我还重新创建了其他类似的帮助器类,以便得到 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();
  }
}

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

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

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

如果你想对注解做一些事情,你也可以将它绑定(bind)到一个参数:

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

在更优雅的 native 语法中具有相同的方面,并且无需使用完全限定的类名,因为您可以使用导入:

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询问如何将连接点匹配限制为仅由特定类调用所产生的那些执行。解决方案是将 execution() 切入点与控制循环切入点结合起来,例如 cflow(call(...) && inside(ClassOfInterest))

但首先让我们用更多的日志输出和第二个应用程序类来扩展测试用例,因为我们需要一个负面测试用例:

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

瞧瞧!

关于java - 具有覆盖接口(interface)方法的特定注释的方法的切入点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42878888/

相关文章:

c# - Ninject:通过属性进行拦截,而不从 InterceptAttribute 派生

java - AspectJ "declare error"未按预期捕获方法。为什么?

带注释参数的 aspectj 切入点

java - Webview和loadurl在另一个类中调用

java - 如何检查和匹配 arraylist 元素的可能组合

java - 如何用SpringBootTest测试一个方面?

java - 第二个带注释的方面未被调用

java - 运行时的切面编织

java - 如何使用 Java 更改 CKFinder 中的 "filebrowserBrowseUrl"和 "filebrowserImageUploadUrl"

java - 如何将我的代码转换为可执行 jar 文件