具有覆盖接口方法的特定注释的方法的切入点
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
瞧瞧!
请考虑以下设置:
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))
.
但是首先让我们用更多的日志输出和第二个应用程序扩展测试用例 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
瞧瞧!