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

时间:2017-03-18 19:28:42

标签: java aop aspectj aspect

请考虑以下设置:

True
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. */ }
}

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

我尝试了许多切入点,但我还没有实现这个目标。到目前为止,我认为最好的切入点如下:

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

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

1 个答案:

答案 0 :(得分:1)

首先,稍微纠正一下:在IVehicle中,方法在声明后必须没有主体但是分号,并且每个方法的public都是多余的,因为接口中声明的所有方法都是公共的。所以它应该看起来像这样至少使它编译:

package de.scrum_master.app;

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

为了让你的例子为我编译,我还重新创建了这样的其他助手类,以便得到一个minimal, complete, and verifiable example(我认为应该是你的工作,BTW):

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()期间才明确执行哪个具体实现类的方法。

请注意,如果您不使用原生的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

如果要对注释执行某些操作,还可以将其绑定到参数:

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

更优雅的原生语法中的相同方面,无需使用完全限定的类名,因为您可以使用导入:

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(...) && within(ClassOfInterest))这样的控制clow切入点组合。

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

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

Etvoilà!