Outils pour utilisateurs

Outils du site


lang:java:aspectj

The AspectJTM Programming Guide Archive du 2003 le 26/04/2020

The AspectJTM 5 Development Kit Developer's Notebook Archive du 2005 le 26/04/2020

Déclaration d'un aspect

Sans annotation

public aspect Log {
}

Avec annotation

import org.aspectj.lang.annotation.Aspect;
 
@Aspect
public class Log {
}

Déclaration d'un point de coupe

Sans annotation

pointcut evaluation(EventHandler h): target(h) &&
  call(public void *.handleEvent());

La dénomination exacte pour trouver le nom d'un méthode est : package.classe.classes internes.methode.

Avec annotation

@Pointcut("call(void C.incI(int)) && args(x) && target(c)")
private void CoupureIncI(int x, C c) {
}
  • evaluation : nom du point de coupe.
  • (EventHandler h) : liste des paramètres qui sont saisis et qui seront disponibles par les méthodes utilisant la coupe. Il faut préciser après chaque paramètre qui est entre les parenthèses.
  • target(h) : h est la target.
Join PointthistargetArguments
Method Callexecuting object*target object**method arguments
Method Executionexecuting object*executing object*method arguments
Constructor Callexecuting object*Noneconstructor arguments
Constructor Executionexecuting objectexecuting objectconstructor arguments
Static initializer executionNoneNoneNone
Object pre-initializationNoneNoneconstructor arguments
Object initializationexecuting objectexecuting objectconstructor arguments
Field referenceexecuting object*target object**None
Field assignmentexecuting object*target object**assigned value
Handler executionexecuting object*executing object*caught exception
Advice executionexecuting aspectexecuting aspectadvice arguments

* There is no executing object in static contexts such as static method bodies or static initializers.

** There is no target object for join points associated with static methods or fields.

  • call : méthode à intercepter. Il est possible d'utiliser execution.

La méthode call est résolue à la compilation alors que la méthode execution est résolue à l'exécution. Pour que la méthode call marche, il faut de la classe appelante soit surveillée alors qu'avec la méthode execution, seule la classe appelée doit être surveillée.

PerfSpy_ Differences between AspectJ call() and execute() Archive du 02/09/2013 le 26/04/2020

  • (public void *.handleEvent()) : la méthode à surveiller.

Pour le constructeur, il utiliser la dénomination new. Par exemple : C.new().

Déclaration d'un advice

Utilisation d'un point de coupe (advice) :

after(EventHandler h):evaluation(h) {}
  • after : s'exécute une fois la méthode terminée. Il existe aussi before ou around :
// Si la fonction update ne renvoie rien.
around() : call(Display.update()) {
  if (! Display.disabled())
    proceed();
}
 
// Si la fonction update renvoie quelque chose.
Object around() : call(Display.update()) {
  if (! Display.disabled())
    return proceed();
  return null;
}

Ici, le point de coupe est directement directement défini dans l'advice. C'est à around de bien appeler proceed(); qui permet l'exécution de la méthode Display.update().

@Around("setAge(i)")
// Mettre void à la place de Object si la méthode setAge ne renvoie rien
public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i) {
  // En cas de méthode static, pas besoin de joinPoint.getTarget().
  return thisJoinPoint.proceed(new Object[]{i*2, joinPoint.getTarget()}); //using Java 5 autoboxing
}

Exemple sans annotation :

Log.aj
public aspect Log {
  pointcut evaluation(EventHandler h): target(h) &&
    call(public void *.handleEvent());
 
  // Le pointcut et after ont la même signature, sinon, ce n'est pas la peine
  // de capturer des éléments dans le pointcut pour ne pas s'en servir.
  after(EventHandler h):evaluation(h) {
    System.out.println("Coucou");
  }
}
ProceedAspect.aj
public aspect ProceedAspect {
  pointcut setAge(int i): call(* setAge(..)) && args(i);
 
  Object around(int i): setAge(i) {
    // Les trois variables thisJoinPoint, thisJoinPointStaticPart et thisEnclosingJoinPointStaticPart
    // s'utilisent sans déclaration. Ce sont des mots clés.
    return proceed(i*2);
  }
}

Et en version annotation Java 5 :

Log.java
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
 
@Aspect
public class Log {
  @After("call(public void *.handleEvent())")
  // Chacun des champs est facultatif.
  public void LogHandleEvent(JoinPoint thisJoinPoint,
    JoinPoint.StaticPart thisJoinPointStaticPart,
    JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart)
  {
    System.out.println("Coucou");
  }
}
ProceedAspect.aj
@Aspect
public class ProceedAspect {
 
  @Pointcut("call(* setAge(..)) && args(i)")
  void setAge(int i) {}
 
  @Around("setAge(i)")
  // Dans le cas d'un Around, JoinPoint est en fait un ProceedingJoinPoint.
  public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, int i) {
    return thisJoinPoint.proceed(new Object[]{i*2});
  }
}

Séparation du point de coupure et de l'advice, il faut passer par une méthode vide. Pas pratique je trouve.

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
 
@Aspect
public class Log {
  @Pointcut("call(public void *.handleEvent())")
  private void CoupureHandleEvent() {
  }
 
  @After("CoupureHandleEvent() && target(h)")
  public void LogHandleEvent(EventHandler h)
  {
    System.out.println("Coucou");
  }
}

Mots clés disponibles dans un advice

  • thisJoinPoint : that contains reflective information about the current join point for the advice to use,
  • thisJoinPointStaticPart : identique à thisJoinPoint.getStaticPart(). Si seulement cette information est nécessaire, il est préférable de l'utiliser plutôt que de passer par thisJoinPoint pour des raisons de performance,
  • thisEnclosingJoinPointStaticPart : This only holds the static part of a join point, but only the enclosing join point.

Exemple avec ProceedingJoinPoint (en annotation donc) :

getArgs - 50 // Ici, un seul argument : un entier.
getKind - method-call
getTarget - test.C@30f39991
getThis - null
getSignature - void test.C.incI(int)
toString - call(void test.C.incI(int))
toLongString - call(void test.C.incI(int))
toShortString - call(C.incI(..))
getSourceLocation - C.java:18

Exemple avec JoinPoint.StaticPart :

getId - 0
getKind - method-call
toLongString - call(void test.C.incI(int))
toShortString - call(C.incI(..))
getClass - class org.aspectj.runtime.reflect.JoinPointImpl$StaticPartImpl
getSignature - void test.C.incI(int)
getSourceLocation - C.java:18

Exemple avec JoinPoint.EnclosingStaticPart :

getId - 1
getKind - method-execution
toLongString - execution(public static void test.C.main(java.lang.String[]))
toShortString - execution(C.main(..))
toString - execution(void test.C.main(String[]))
getSignature - void test.C.main(String[])
getSourceLocation - C.java:16

Accéder aux champs privés

Mot clé : privileged. Cela n'existe pas en annotation et ne s'utilise pas en combiné avec l'annotation @Aspect.

class C {
  private int i = 0;
  void incI(int x) { i = i+x; }
}
privileged aspect A {
  static final int MAX = 1000;
  before(int x, C c): call(void C.incI(int)) && target(c) && args(x) {
    if (c.i+x > MAX) throw new RuntimeException();
  }
}

Mélange avec annotation et sans annotation

C'est niet.

Il est interdit de mélanger l'écriture de code avec annotation et sans annotation. Il n'est donc pas possible d'utiliser la moindre annotation dans une classe nécessitant d'avoir des droits privilégiés.

java 8 - AspectJ and Java8 - bad type on operand stack - Stack Overflow Archive du 30/07/2014 le 26/04/2020

C.java
public class C {
  public int i = 0;
 
  void incI(int x) {
    System.out.println(i);
    i = i + x;
  }
 
  static public void main(String[] args) {
    C c = new C();
    c.incI(50);
  }
}
A.aj
import org.aspectj.lang.annotation.Around;
//import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
 
//@Aspect
public aspect A {
  static final int MAX = 1000;
 
  @Pointcut("call(void C.incI(int)) && args(x) && target(c)")
  private void CoupureIncI(int x, C c) {
  }
 
  @Around("CoupureIncI(x, c)")
  public void incIA(int x, C c) {
    if (c.i + x > MAX)
      throw new RuntimeException();
  }
}

Résultat à l'exécution :

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    test/C.main([Ljava/lang/String;)V @21: invokestatic
  Reason:
    Type 'test/C' (current frame, stack[4]) is not assignable to integer
  Current Frame:
    bci: @21
    flags: { }
    locals: { '[Ljava/lang/String;', 'test/C', integer, 'test/C' }
    stack: { 'test/C', integer, 'test/A', integer, 'test/C', null }
  Bytecode:
    0x0000000: bb00 0159 b700 234c 2b10 323d 4e2d 1cb8
    0x0000010: 0036 1c2d 01b8 003a b1                 

      at java.lang.Class.getDeclaredMethods0(Native Method)
      at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
      at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
      at java.lang.Class.getMethod0(Class.java:3018)
      at java.lang.Class.getMethod(Class.java:1784)
      at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
      at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)

AspectJ en interne

Le compilateur ajc utilise les points de coupe et les advices pour scanner le code source et trouver toutes les méthodes appelantes et les encadre avant par un appel des méthodes before, et après par un appel des méthodes after.

Exemple avec before

Code source

C.java
public class C {
  private int i = 0;
 
  void incI(int x) {
    i = i + x;
  }
 
  static public void main (String[] arg) {
    C c = new C();
    c.incI(50);
    c.incI(1000);
  }
}
A.aj
public privileged aspect A {
  static final int MAX = 1000;
 
  before(int x, C c): call(void C.incI(int)) && target(c) && args(x) {
    if (c.i + x > MAX)
      throw new RuntimeException();
  }
}

Version décompilée avec l'advice sur la méthode ''call''

C.java
public class C
{
  // Ajout de getter/setter static pour permettre d'avoir accès à la variable privée i.
  public static int ajc$get$i(C paramC)
  {
    return paramC.i;
  }
 
  public static void ajc$set$i(C paramC, int paramInt)
  {
    paramC.i = paramInt;
  }
 
  private int i = 0;
 
  void incI(int x)
  {
    this.i += x;
  }
 
  public static void main(String[] arg)
  {
    // Le code before entoure chaque appel.
    C c = new C();
    int j = 50;C localC1 = c;A.aspectOf().ajc$before$test_A$1$ff7f72c0(j, localC1);localC1.incI(j);
    int k = 1000;C localC2 = c;A.aspectOf().ajc$before$test_A$1$ff7f72c0(k, localC2);localC2.incI(k);
  }
}
A.java
import org.aspectj.internal.lang.annotation.ajcPrivileged;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
@ajcPrivileged
public class A
{
  static final int MAX = 1000;
 
  public static A aspectOf()
  {
    if (ajc$perSingletonInstance == null) {
      throw new NoAspectBoundException("test_A", ajc$initFailureCause);
    }
    return ajc$perSingletonInstance;
  }
 
  public static boolean hasAspect()
  {
    return ajc$perSingletonInstance != null;
  }
 
  static
  {
    try
    {
 
    }
    catch (Throwable localThrowable)
    {
      ajc$initFailureCause = localThrowable;
    }
  }
 
  @Before(value="(call(void C.incI(int)) && (target(c) && args(x)))", argNames="x,c")
  public void ajc$before$test_A$1$ff7f72c0(int x, C c)
  {
    if (C.ajc$get$i(c) + x > 1000) {
      throw new RuntimeException();
    }
  }
}

Version décompilée avec l'advice sur la méthode ''execution''

C.java
public class C
{
  public static int ajc$get$i(C paramC)
  {
    return paramC.i;
  }
 
  public static void ajc$set$i(C paramC, int paramInt)
  {
    paramC.i = paramInt;
  }
 
  private int i = 0;
 
  void incI(int x)
  {
    // Le code before entoure l'intérieur de la méthode surveillée
    int j = x;A.aspectOf().ajc$before$test_A$1$74272596(j, this);this.i += x;
  }
 
  public static void main(String[] arg)
  {
    C c = new C();
    c.incI(50);
    c.incI(1000);
  }
}
A.java
import org.aspectj.internal.lang.annotation.ajcPrivileged;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
@ajcPrivileged
public class A
{
  static final int MAX = 1000;
 
  public static A aspectOf()
  {
    if (ajc$perSingletonInstance == null) {
      throw new NoAspectBoundException("test_A", ajc$initFailureCause);
    }
    return ajc$perSingletonInstance;
  }
 
  public static boolean hasAspect()
  {
    return ajc$perSingletonInstance != null;
  }
 
  static
  {
    try
    {
 
    }
    catch (Throwable localThrowable)
    {
      ajc$initFailureCause = localThrowable;
    }
  }
 
  @Before(value="(execution(void C.incI(int)) && (target(c) && args(x)))", argNames="x,c")
  public void ajc$before$test_A$1$74272596(int x, C c)
  {
    if (C.ajc$get$i(c) + x > 1000) {
      throw new RuntimeException();
    }
  }
}

Exemple avec around

Code source

C.java
public class C {
  public int i = 0;
 
  void incI(int x) {
    System.out.println("incI" + i);
    i = i + x;
  }
 
  void incI() {
    System.out.println("incI" + i);
    i = i + 1;
  }
 
  static public void main(String[] arg) {
    C c = new C();
    c.incI(50);
    c.incI(500);
    c.incI(1500);
  }
}
A.aj
public privileged aspect A {
  private static final int MAX = 1000;
 
  void around(C c, int x): call(void C.incI(int)) && target(c) && args(x) {
    if (c.i + x > MAX)
      throw new RuntimeException();
    proceed(c, x);
  }
}
A2.aj
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
 
@Aspect
public class A2 {
  private static final int MAX = 1000;
 
  @Pointcut("call(void C.incI(int)) && args(x)")
  private void CoupureIncI(int x) {
  }
 
  @Around("CoupureIncI(x)")
  // AutourDeCoupureIncI renvoie le même type que C.incI.
  public void AutourDeCoupureIncI(ProceedingJoinPoint joinPoint, int x) throws Throwable {
    if (((C) joinPoint.getTarget()).i + x > MAX)
      throw new RuntimeException();
    // En cas de méthode static, pas besoin de joinPoint.getTarget().
    joinPoint.proceed(new Object[] { x, joinPoint.getTarget() });
  }
}

Version décompilée avec l'advice A seulement

C.java
import java.io.PrintStream;
import org.aspectj.runtime.internal.AroundClosure;
 
public class C
{
  public int i = 0;
 
  void incI(int x)
  {
    System.out.println("incI" + this.i);
    this.i += x;
  }
 
  private static final void incI_aroundBody1$advice(C target, int x, A ajc$aspectInstance, C c, int x, AroundClosure ajc$aroundClosure)
  {
    if (c.i + x > 1000) {
      throw new RuntimeException();
    }
    AroundClosure localAroundClosure = ajc$aroundClosure;int j = x;C localC = c;incI_aroundBody0(localC, j);
  }
 
  private static final void incI_aroundBody3$advice(C target, int x, A ajc$aspectInstance, C c, int x, AroundClosure ajc$aroundClosure)
  {
    if (c.i + x > 1000) {
      throw new RuntimeException();
    }
    AroundClosure localAroundClosure = ajc$aroundClosure;int j = x;C localC = c;incI_aroundBody2(localC, j);
  }
 
  private static final void incI_aroundBody5$advice(C target, int x, A ajc$aspectInstance, C c, int x, AroundClosure ajc$aroundClosure)
  {
    if (c.i + x > 1000) {
      throw new RuntimeException();
    }
    AroundClosure localAroundClosure = ajc$aroundClosure;int j = x;C localC = c;incI_aroundBody4(localC, j);
  }
 
  void incI()
  {
    System.out.println("incI" + this.i);
    this.i += 1;
  }
 
  private static final void incI_aroundBody0(C paramC, int paramInt)
  {
    paramC.incI(paramInt);
  }
 
  private static final void incI_aroundBody2(C paramC, int paramInt)
  {
    paramC.incI(paramInt);
  }
 
  public static void main(String[] arg)
  {
    C c = new C();
    int j = 50;C localC1 = c;incI_aroundBody1$advice(localC1, j, A.aspectOf(), localC1, j, null);
    int k = 500;C localC2 = c;incI_aroundBody3$advice(localC2, k, A.aspectOf(), localC2, k, null);
    int m = 1500;C localC3 = c;incI_aroundBody5$advice(localC3, m, A.aspectOf(), localC3, m, null);
  }
 
  private static final void incI_aroundBody4(C paramC, int paramInt)
  {
    paramC.incI(paramInt);
  }
}
A.aj
import org.aspectj.internal.lang.annotation.ajcPrivileged;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.runtime.internal.AroundClosure;
 
@Aspect
@ajcPrivileged
public class A
{
  private static final int MAX = 1000;
 
  public static A aspectOf()
  {
    if (ajc$perSingletonInstance == null) {
      throw new NoAspectBoundException("test_A", ajc$initFailureCause);
    }
    return ajc$perSingletonInstance;
  }
 
  public static boolean hasAspect()
  {
    return ajc$perSingletonInstance != null;
  }
 
  static
  {
    try
    {
 
    }
    catch (Throwable localThrowable)
    {
      ajc$initFailureCause = localThrowable;
    }
  }
 
  @Around(value="(call(void C.incI(int)) && (target(c) && args(x)))", argNames="c,x,ajc$aroundClosure")
  public void ajc$around$test_A$1$ff7f72c0(C c, int x, AroundClosure ajc$aroundClosure)
  {
    if (c.i + x > 1000) {
      throw new RuntimeException();
    }
    ajc$around$test_A$1$ff7f72c0proceed(c, x, ajc$aroundClosure);
  }
}

Version décompilée avec l'advice A2 seulement

C.java
import java.io.PrintStream;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.runtime.internal.Conversions;
import org.aspectj.runtime.reflect.Factory;
 
public class C
{
  private static void ajc$preClinit()
  {
    Factory localFactory = new Factory("C.java", C.class);ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("0", "incI", "test.C", "int", "x", "", "void"), 18);ajc$tjp_1 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("0", "incI", "test.C", "int", "x", "", "void"), 19);ajc$tjp_2 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("0", "incI", "test.C", "int", "x", "", "void"), 20);
  }
 
  public int i = 0;
  private static final JoinPoint.StaticPart ajc$tjp_0;
  private static final JoinPoint.StaticPart ajc$tjp_1;
  private static final JoinPoint.StaticPart ajc$tjp_2;
 
  void incI(int x)
  {
    System.out.println("incI" + this.i);
    this.i += x;
  }
 
  void incI()
  {
    System.out.println("incI" + this.i);
    this.i += 1;
  }
 
  private static final void incI_aroundBody0(C paramC, int paramInt, JoinPoint paramJoinPoint)
  {
    paramC.incI(paramInt);
  }
 
  private static final void incI_aroundBody2(C paramC, int paramInt, JoinPoint paramJoinPoint)
  {
    paramC.incI(paramInt);
  }
 
  public static void main(String[] arg)
  {
    C c = new C();
    int j = 50;C localC1 = c;JoinPoint localJoinPoint1 = Factory.makeJP(ajc$tjp_0, null, localC1, Conversions.intObject(j));incI_aroundBody1$advice(localC1, j, localJoinPoint1, A2.aspectOf(), (ProceedingJoinPoint)localJoinPoint1, j);
    int k = 500;C localC2 = c;JoinPoint localJoinPoint2 = Factory.makeJP(ajc$tjp_1, null, localC2, Conversions.intObject(k));incI_aroundBody3$advice(localC2, k, localJoinPoint2, A2.aspectOf(), (ProceedingJoinPoint)localJoinPoint2, k);
    int m = 1500;C localC3 = c;JoinPoint localJoinPoint3 = Factory.makeJP(ajc$tjp_2, null, localC3, Conversions.intObject(m));incI_aroundBody5$advice(localC3, m, localJoinPoint3, A2.aspectOf(), (ProceedingJoinPoint)localJoinPoint3, m);
  }
 
  private static final void incI_aroundBody4(C paramC, int paramInt, JoinPoint paramJoinPoint)
  {
    paramC.incI(paramInt);
  }
 
  private static final void incI_aroundBody1$advice(C target, int x, JoinPoint thisJoinPoint, A2 ajc$aspectInstance, ProceedingJoinPoint joinPoint, int x)
  {
    if (((C)joinPoint.getTarget()).i + x > 1000) {
      throw new RuntimeException();
    }
    Object[] arrayOfObject = { Integer.valueOf(x), joinPoint.getTarget() };ProceedingJoinPoint localProceedingJoinPoint = joinPoint;incI_aroundBody0(target, Conversions.intValue(arrayOfObject[0]), localProceedingJoinPoint);null;
  }
 
  private static final void incI_aroundBody3$advice(C target, int x, JoinPoint thisJoinPoint, A2 ajc$aspectInstance, ProceedingJoinPoint joinPoint, int x)
  {
    if (((C)joinPoint.getTarget()).i + x > 1000) {
      throw new RuntimeException();
    }
    Object[] arrayOfObject = { Integer.valueOf(x), joinPoint.getTarget() };ProceedingJoinPoint localProceedingJoinPoint = joinPoint;incI_aroundBody2(target, Conversions.intValue(arrayOfObject[0]), localProceedingJoinPoint);null;
  }
 
  private static final void incI_aroundBody5$advice(C target, int x, JoinPoint thisJoinPoint, A2 ajc$aspectInstance, ProceedingJoinPoint joinPoint, int x)
  {
    if (((C)joinPoint.getTarget()).i + x > 1000) {
      throw new RuntimeException();
    }
    Object[] arrayOfObject = { Integer.valueOf(x), joinPoint.getTarget() };ProceedingJoinPoint localProceedingJoinPoint = joinPoint;incI_aroundBody4(target, Conversions.intValue(arrayOfObject[0]), localProceedingJoinPoint);null;
  }
 
  static {}
}
A2.aj
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
 
@Aspect
public class A2
{
  private static final int MAX = 1000;
  private static Throwable ajc$initFailureCause;
  public static final A2 ajc$perSingletonInstance;
 
  static
  {
    try
    {
      ajc$postClinit();
    }
    catch (Throwable localThrowable)
    {
      ajc$initFailureCause = localThrowable;
    }
  }
 
  private static void ajc$postClinit()
  {
    ajc$perSingletonInstance = new A2();
  }
 
  public static boolean hasAspect()
  {
    return ajc$perSingletonInstance != null;
  }
 
  public static A2 aspectOf()
  {
    if (ajc$perSingletonInstance == null) {
      throw new NoAspectBoundException("test.A2", ajc$initFailureCause);
    }
    return ajc$perSingletonInstance;
  }
 
  @Around("CoupureIncI(x)")
  public void AutourDeCoupureIncI(ProceedingJoinPoint joinPoint, int x)
    throws Throwable
  {
    if (((C)joinPoint.getTarget()).i + x > 1000) {
      throw new RuntimeException();
    }
    joinPoint.proceed(new Object[] { Integer.valueOf(x), joinPoint.getTarget() });
  }
}

Il n'y a finalement pas de différence entre la méthode avec annotation et sans annotation, hormis que l'ordre d'écriture des méthodes n'est pas la même (et bien sûr l'utilisation de ProceedingJoinPoint pour une annotation around et JoinPoint pour before et after).

Héritage et interface

aspect A
{
  private interface HasName {}
  declare parents: (Point || Line || Square) implements HasName;
 
  private String HasName.name;
  public  String HasName.getName()  { return name; }
}

Cela indique explicitement que Point, Line et Square implémente HasName et que on ajoute un attribut name et une méthode getName accessibles uniquement par les classes aspects.

Il s'agit d'une interface aspect. Donc les méthodes qui seront dans cette interface sont également des méthodes aspect et donc uniquement accessible depuis des classes aspect.

aspect A
{
  private interface HasName
  {
    public String getNom();
  }
 
  declare parents: Point implements HasName;
 
  public String Point.getNom()
  {
    return;
  }
}

Aspect à l'exécution

Pour cela, il faut un JAR contenant l'aspect, un JAR contenant la classe et un programme fusionnant les 2.

La classe

Créez un Java project.

C.java
package classe;
 
public class C {
  public int i = 0;
 
  public void incI(int x) {
    i = i + x;
  }
 
  static public void main(String[] arg) {
    for (String string : arg) {
      System.out.println(string);
    }
    C c = new C();
    c.incI(50);
    c.incI(1000);
  }
}

Puis en console allez dans le dossier bin et tapez :

jar cf C.jar classe

L'aspect

Créez un AspectJ project.

A.aj
package aspects;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
 
import classe.C;
 
// Les deux implémentations marchent.
 
public aspect A {
  static final int MAX = 1000;
 
  before(int x, C c): call(void C.incI(int)) && target(c) && args(x) {
    System.out.println("Before");
    if (c.i + x > MAX)
      throw new RuntimeException();
  }
}
 
/*
@Aspect
public class A {
  private static final int MAX = 1000;
 
  @Around("execution(void C.incI(int)) && args(x)")
  // AutourDeCoupureIncI renvoie le même type que C.incI.
  public void AutourDeCoupureIncI(ProceedingJoinPoint joinPoint, int x) throws Throwable {
    System.out.println("début");
    if (((C) joinPoint.getTarget()).i + x > MAX)
      throw new RuntimeException();
 
    joinPoint.proceed(new Object[] { x });
    System.out.println("Fin");
  }
}
*/

La classe dépendant de C, il ne faut pas oublie d'ajouter le chemin vers le premier projet pour que la classe compile : Menu Project|Properties, catégorie Java Build Path, onglet Projects puis Add et cochez la case du projet Java classe.

Puis en console allez dans le dossier bin et tapez :

jar cf A.jar aspects

La classe exécutrice

Créez un AspectJ project.

Main.java
package main;
 
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
 
import org.aspectj.weaver.loadtime.WeavingURLClassLoader;
 
public class Main {
  static public void main(String[] arg) throws ClassNotFoundException, NoSuchMethodException,
      SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    try (WeavingURLClassLoader weaving = new WeavingURLClassLoader(
        new URL[] { new URL("file:///tmp/java/classe/bin/C.jar"), new URL("file:///tmp/java/aspects/bin/A.jar") },
        new URL[] { new URL("file:///tmp/java/aspects/bin/A.jar") },
        Thread.currentThread().getContextClassLoader())) {
      Thread.currentThread().setContextClassLoader(weaving);
 
      Class<?> classC = weaving.loadClass("classe.C");
 
      Method mainMethod = classC.getMethod("main", new Class[] { String[].class });
 
      mainMethod.invoke(null, (Object)new String[] { "Start" });
    }
  }
}

Pensez à ajouter le jar org.aspectj.weaver dans le Build Path, onglet Libraries.

Exécution

Start
Before
Before
Exception in thread "main" java.lang.reflect.InvocationTargetException
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at main.Main.main(Main.java:24)
Caused by: java.lang.RuntimeException
  at aspects.A.ajc$before$aspects_A$1$ff7f72c0(A.aj:18)
  at classe.C.main(C.java:16)
  ... 5 more

Commentaires

Il est impératif que le projet main ne possède pas le projet classe dans son Build Path, onglet Projects. Sinon la réflexion ne passera pas par l'aspect.

Kilim.java, Archive kilim.zip

lang/java/aspectj.txt · Dernière modification : 2020/04/26 22:39 de root