Руководство Java Aspect Oriented Programming с AspectJ (AOP)

....

1- Введение

Данная статья основана на:
  • Eclipse 4.4 (LUNA)

  • AspectJ 1.8.2

2- Требования перед началом

Вам необходимо установить инструмент разработки AspectJ в Eclipse, вы можете ознакомиться с инструкциями по ссылке:

3- Классы участвующие в примере

В данной статье я использую некоторые классы, которые участвуют в иллюстрированном примере  AspectJ.
  • Box
  • FigureElement
  • Group
  • Line
  • Point
  • ShapeFigureElement
  • SlotfulPoint
Источник этих классов взяты из:
Box.java
package figures;

import java.awt.Rectangle;
import java.awt.Shape;

public class Box extends ShapeFigureElement {
  private Point _p0;
  private Point _p1;
  private Point _p2;
  private Point _p3;

  public Box(int x0, int y0, int width, int height) {
      _p0 = new Point(x0, y0);
      _p1 = new Point(x0 + width, y0);
      _p2 = new Point(x0 + width, y0 + height);
      _p3 = new Point(x0, y0 + height);
  }

  public Point getP0() {
      return _p0;
  }

  public Point getP1() {
      return _p1;
  }

  public Point getP2() {
      return _p2;
  }

  public Point getP3() {
      return _p3;
  }

  @Override
  public void move(int dx, int dy) {
      _p0.move(dx, dy);
      _p1.move(dx, dy);
      _p2.move(dx, dy);
      _p3.move(dx, dy);
  }

  public void checkBoxness() {
      if ((_p0.getX() == _p3.getX()) && (_p1.getX() == _p2.getX())
              && (_p0.getY() == _p1.getY()) && (_p2.getY() == _p3.getY()))
          return;
      throw new IllegalStateException("This is not a square.");
  }

  @Override
  public String toString() {
      return "Box(" + _p0 + ", " + _p1 + ", " + _p2 + ", " + _p3 + ")";
  }

  @Override
  public Shape getShape() {
      return new Rectangle(getP1().getX(), getP1().getY(), getP3().getX()
              - getP1().getX(), getP3().getY() - getP1().getY());
  }
}
FigureElement.java
package figures;

import java.awt.*;
import java.awt.geom.*;

public interface FigureElement {
  public static final Rectangle MAX_BOUNDS = new Rectangle(0, 0, 500, 500);

  public abstract void move(int dx, int dy);

  public abstract Rectangle getBounds();

  public abstract boolean contains(Point2D p);

  public abstract void paint(Graphics2D g2);
}
Group.java
package figures;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Group implements FigureElement {
  private Collection _members;
  private String _identifier;

  public Group(FigureElement first) {
      this._members = new ArrayList();
      add(first);
  }

  public void add(FigureElement fe) {
      _members.add(fe);
  }

  public Iterator members() {
      return _members.iterator();
  }

  public void move(int dx, int dy) {
      for (Iterator i = _members.iterator(); i.hasNext();) {
          FigureElement fe = (FigureElement) i.next();
          fe.move(dx, dy);
      }
  }

  public void setIdentifier(String identifier) {
      _identifier = identifier;
  }

  @Override
  public String toString() {
      if (_identifier != null) {
          return _identifier;
      }
      StringBuffer buf = new StringBuffer("Group(");
      for (Iterator i = _members.iterator(); i.hasNext();) {
          buf.append(i.next().toString());
          if (i.hasNext()) {
              buf.append(", ");
          }
      }
      buf.append(")");
      return buf.toString();
  }

  public Rectangle getBounds() {
      Rectangle previous = null;
      for (Iterator i = _members.iterator(); i.hasNext();) {
          FigureElement fe = (FigureElement) i.next();
          Rectangle rect = fe.getBounds();
          if (previous != null) {
              previous = previous.union(rect);
          } else {
              previous = rect;
          }
      }
      return previous;
  }

  public boolean contains(Point2D p) {
      for (Iterator i = _members.iterator(); i.hasNext();) {
          FigureElement fe = (FigureElement) i.next();
          if (fe.contains(p))
              return true;
      }
      return false;
  }

  public void paint(Graphics2D g2) {
      for (Iterator i = _members.iterator(); i.hasNext();) {
          FigureElement fe = (FigureElement) i.next();
          fe.paint(g2);
      }
  }

  public int size() {
      return _members.size();
  }
}
Line.java
package figures;

import java.awt.*;
import java.awt.geom.*;

public class Line extends ShapeFigureElement {
  private Point _p1;
  private Point _p2;

  public Line(Point p1, Point p2) {
      _p1 = p1;
      _p2 = p2;
  }

  public Point getP1() {
      return _p1;
  }

  public Point getP2() {
      return _p2;
  }

  @Override
  public void move(int dx, int dy) {
      _p1.move(dx, dy);
      _p2.move(dx, dy);
  }

  @Override
  public String toString() {
      return "Line(" + _p1 + ", " + _p2 + ")";
  }

  /**
   * Used to determine if this line {@link contains(Point2D)} a point.
   */
  final static int THRESHHOLD = 5;

  /**
   * Returns <code>true</code> if the point segment distance is less than
   * {@link THRESHHOLD}.
   */
  @Override
  public boolean contains(Point2D p) {
      return getLine2D().ptLineDist(p) < THRESHHOLD;
  }

  private Line2D getLine2D() {
      return new Line2D.Float((float) getP1().getX(), (float) getP1().getY(),
              (float) getP2().getX(), (float) getP2().getY());
  }

  public Shape getShape() {
      return getLine2D();
  }
}
Point.java
package figures;

import java.awt.*;
import java.awt.geom.*;

public class Point extends ShapeFigureElement {
  private int _x;
  private int _y;

  public Point(int x, int y) {
      _x = x;
      _y = y;
  }

  public int getX() {
      return _x;
  }

  public int getY() {
      return _y;
  }

  public void setX(int x) {
      _x = x;
  }

  public void setY(int y) {
      _y = y;
  }

  @Override
  public void move(int dx, int dy) {
      _x += dx;
      _y += dy;
  }

  @Override
  public String toString() {
      return "Point(" + _x + ", " + _y + ")";
  }

  /** The height of displayed {@link Point}s. */
  private final static int HEIGHT = 10;
  /** The width of displayed {@link Point}s. -- same as {@link HEIGHT}. */
  private final static int WIDTH = Point.HEIGHT;

  @Override
  public Shape getShape() {
      return new Ellipse2D.Float((float) getX() - Point.WIDTH / 2,
              (float) getY() - Point.HEIGHT / 2, (float) Point.HEIGHT,
              (float) Point.WIDTH);
  }
}
ShapeFigureElement.java
package figures;

import java.awt.*;
import java.awt.geom.*;

public abstract class ShapeFigureElement implements FigureElement {
  @Override
  public abstract void move(int dx, int dy);

  public abstract Shape getShape();

  @Override
  public Rectangle getBounds() {
      return getShape().getBounds();
  }

  @Override
  public boolean contains(Point2D p) {
      return getShape().contains(p);
  }

  public Color getLineColor() {
      return Color.black;
  }

  public Color getFillColor() {
      return Color.red;
  }

  @Override
  public final void paint(Graphics2D g2) {
      Shape shape = getShape();
      g2.setPaint(getFillColor());
      g2.fill(shape);
      g2.setPaint(getLineColor());
      g2.draw(shape);
  }
}
SlothfulPoint.java
package figures;

import java.awt.*;
import java.awt.geom.*;

/**
* This class makes mistakes to be caught by invariant checkers.
*/
public class SlothfulPoint extends ShapeFigureElement {
  private int _x;
  private int _y;

  public SlothfulPoint(int x, int y) {
  }

  public int getX() {
      return _x;
  }

  public int getY() {
      return _y;
  }

  public void setX(int x) {
  }

  public void setY(int y) {
  }

  @Override
  public void move(int dx, int dy) {
      System.out.println("Slothful moving");
  }

  @Override
  public String toString() {
      return "SlothfulPoint";
  }

  @Override
  public Shape getShape() {
      return new Ellipse2D.Float((float) _x, (float) _y, 1.0f, 1.0f);
  }
}

4- Создать AspectJ Project

Для начала создайте обычный проект с названием  AspectJTutorial.
Кликните правой кнопкой мыши на Project и выберите  Configure/Convert to AspectJ Project.
Convert завершен:

5- Первый пример с AspectJ

Для начала, мы используем первый пример, прежде чем начинать с понятиями:
Создайте класс  HelloAspectJDemo:
HelloAspectJDemo.java
package org.o7planning.tutorial.aspectj.helloaspectj;

public class HelloAspectJDemo {

  public static void sayHello() {
      System.out.println("Hello");
  }

  public static void greeting()  {
     
      String name = new String("John");
     
      sayHello();
     
      System.out.print(name);
  }
 
 
  public static void main(String[] args) {        
     
      sayHello();
     
      System.out.println("--------");
     
      sayHello();
     
      System.out.println("--------");
     
      greeting();
     
     
  }

}
На класс выше не стоит обращать внимания, однако проблема заключается в том, что если вы хотите, чтобы программа делала что-то прямо перед или после вызова метода sayHello(), например, печать объявления на экране. Традиционно вы можете добавить команду, печатающую объявление прямо на экране перед или после вызова метода sayHello().
AspectJ - это своего рода аспектное программирование. Он предоставляет вам другое решение для решения этой проблемы, но  AspectJ может сделать еще больше. Помните, что это просто пример ​​​​​​​ HelloWorld.
  • File/New/Other..
Создан файл с названием  HelloAspectJ.aj, и его структура очень похожа на класс.
Предыдущие версии AspectJ используют аннотацию (annotation) для описания. В последних версиях используется файл * .aj. В этой статье я пропускаю использование аннотации, поскольку это не так ясно, как использование файла .aj. Кроме того, синтаксис файла * aj похож на класс, и eclipse будет уведомлять вас о ваших грамматических ошибках.
Редактирование кода  HelloAspectJ.aj:
HelloAspectJ.aj
package org.o7planning.tutorial.aspectj.helloaspectj;

public aspect HelloAspectJ {

    // Define a Pointcut is
    // collection of JoinPoint call sayHello of class HelloAspectJDemo.
    pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());

    before() : callSayHello() {
        System.out.println("Before call sayHello");
    }

    after() : callSayHello()  {
        System.out.println("After call sayHello");
    }

    
}
AspectJ имеет некоторые отличия от типичного класса, в котором он добавляет много ключевых слов. Его имя файла имеет хвост * .aj вместо * .java. Некоторые ключевые слова для AspectJ:
  • aspect   pointcut   privileged   call   execution  
  • initialization   preinitialization   handler   get   set  
  • staticinitialization   target   args   within   withincode  
  • cflow   cflowbelow   annotation   before   after   around  
  • proceed   throwing   returning   adviceexecution   declare  
  • parents   warning   error   soft   precedence   thisJoinPoint  
  • thisJoinPointStaticPart   thisEnclosingJoinPointStaticPart  
  • issingleton   perthis   pertarget   percflow   percflowbelow  
  • pertypewithin   lock   unlock   thisAspectInstance
Вернемся к примеру HelloWorld. Теперь мы запустим класс HelloAspectJDemo, и результат будет таким:
AspectJ очень силен. Например, в приведенном выше примере исправление кода на HelloAspectJ.aj может заставить его работать с каждым классом, вызывающим метод sayHello ().
// Define a pointcut is a Collection of JoinPoint
// call sayHello() of HelloAspectJDemo
pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());


// Change to:
// This means that everywhere (class, AspectJ) called sayHello()
pointcut callSayHello(): call(* *.sayHello());

6- Базовые понятия в AspectJ

В  AspectJ вам нужно различать несколько понятий:
  • Advice
  • Pointcut
  • JoinPoint

6.1- JoinPoint

JoinPoint (точка объединения) является четко определенной точкой в ​​потоке программы.
  • Мы хотим выполнить определенный код ("advice") при каждом достижении точки объединения
  • Мы не хотим загромождать код явными индикаторами, говорящими: "Это точка объединения",
    • То есть не вписываем код сами в это место
  • AspectJ предоставляет синтаксис для указания этих JoinPoint "извне" фактического кода.
    Отметить их и выполнить определенный код извне.

JoinPoint - это точка в программном потоке "Где происходит что-то определенное", что-то здесь может быть:
  • Когда метод вызывается
  • Когда выбрасывается исключение
  • Когда получен доступ (accessed)
  • При инициализации объекта
  • При ссылке к объекту
Таким образом  JoinPoint очень разнообразен, точка где вы инициализируете объект тоже рассматривается как  JoinPoint.

Возвращаемся к примеру HelloWorld, мы определим некоторые  JoinPoints:

6.2- PointCut

Определение  Pointcut включает один слева и один справа, отделенные друг от друга двоеточием.
  • На левой стороне включает названия pointcut и параметры pointcut (например, готовые данные когда происходят события)
  • На правой сторне содержится сам Pointcut
Пример:
pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());
  • Название этого pointcut callSayHello
  • У pointcut нет параметров
  • Сам pointcut является call (* HelloAspectJDemo.sayHello())
  • Pointcut ссылается на любое время, когда вызывается метод HelloAspectJDemo.sayHello()

6.3- Advice

Возвращаемся к примеру  HelloWorld, У нас есть 2 advice (совет)
  • before()
  • after()
Advice (совет) определяет поведение. Код Advice запускается в каждой точке объединения (join point) в Pointcut. То, как работает код, зависит от вида  Advice (совета).
AspectJ поддерживает три вида Advice. Виды Advice определяют, как он взаимодействует с JoinPoint (точками объединения), которые он определяет. Таким образом, AspectJ делится на до (before), после (after) запуска JoinPoint, и вокруг (around) (на месте или «рядом») JoinPoint.
Во то время как  "before Advice" относительно не имеет проблем, с "after Advice" могут быть 3 ситуации: после того, как выполнение  JoinPoint завершается нормально, после того как выбрасывается исключение или после того, как оно делает одно из двух. AspectJ позволяет "after Advice" для любой из этих ситуаций.
Чтобы легче было понять, посмотрим несколько примеров  Advice:
HelloAspectJ2.aj
package org.o7planning.tutorial.aspectj.helloaspectj;

public aspect HelloAspectJ2 {

    pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());

    // Advice "after returning".
    after() returning (Object retObj): callSayHello() {
        System.out.println("Returned normally with " + retObj);
    }

    // Advice "after throwing".
    after() throwing (Exception e): callSayHello() {
        System.out.println("Threw an exception: " + e);
    }

    // Advice "after returning" + "after throwing".
    after() : callSayHello()  {
        System.out.println("Returned or threw an Exception");
    }
}
Вид Advice " after returning" может не беспокоиться о возвращаемом значении (метода), поэтому мы можем записать.
// Advice "after returning".
// care about the returning value of method
after() returning (Object retObj): callSayHello() {
  System.out.println("Returned normally with " + retObj);
}

// Advice "after returning" not care about the returning value of method
after() returning(): callSayHello() {
  System.out.println("Returned normally");
}

// or
// Advice "after returning" not care about the returning value of method
after() returning : callSayHello() {
  System.out.println("Returned normally");
}

7- Пример базового AspectJ 

Прежде чем перейти к повышенным деталям, мы рассмотрим базовые примеры и правила в AspectJ, если вы не поймете эти правила, вы не сможете понять AspectJ.

7.1- Правила для class, method, * и ..

В AspectJ если вы хотите упомянуть класс, метод в объявлении Pointcut вам нужно написать его полное название. Например:
  • figures.Point
  • figures.Point.setX(int)
Если класс и AspectJ находятся в одном пакете, вы можете написать вкратце. Например:
  • Point
  • Point.setX(int)
Давайте посмотрим на следующую иллюстрацию: AspectJ01.aj и class Point находятся в двух разных пакетах.
AspectJ01.aj
package org.o7planning.tutorial.aspectj.demo01;

public aspect AspectJ01 {

    // Class Point and AspectJ is not the same package
    // so must specify the package (Required).
    // This pointcut definition of JoinPoints
    // only within the class ClassTest01
    // This ClassTest01 and AspectJ same package,
    // so can be ignored package in within(..).
    pointcut callSetX()  : call(void  figures.Point.setX(int)) && within (ClassTest01) ;

    // Advice
    before() : callSetX()  {
        System.out.println("Before call Point.setX(int)");
    }
}
ClassTest01.java
package org.o7planning.tutorial.aspectj.demo01;

import figures.Point;

public class ClassTest01 {

  public static void main(String[] args) {

      Point point = new Point(10, 200);
     
      System.out.println("---- (1) ----");
     
      point.setX(20);
     
      System.out.println("---- (2) ----");
     
      point.setY(100);
     
      System.out.println("---- (3) ----");
  }

}
Результат запуска class ClassTest01:
Таким образом здесь есть несколько примечаний:
// 'within' used to limit the scope of the pointcut
// in the case below:
// Only Containing join point inside class ClassTest01.
within (ClassTest01)

// If you using import
import figures.Point;
.....
// Then Can write shorter code:
pointcut callSetX()  : call(void  Point.setX(int)) && within (ClassTest01) ;
Звездочка (*) в AspectJ
// Collection of JoinPoints call Point.setX(int), any package name,
// and method return  void
pointcut callSetX()  : call(void  *.Point.setX(int)) ;

// Collection of JoinPoints call Point.setX(int), any package name,
// and method return any
pointcut callSetX()  : call(* *.Point.setX(int)) ;


 
// Collection of JoinPoints call public static method setX(int) of class with package name is 'sample'
// and class name have suffix Point,
// and setX(int) return int
pointcut callSetX()  : call(public static int sample.*Point.setX(int)) ;


// Using (..) to describe the method has 0 or more parameters.
pointcut callSetX()  : call(public static int sample.*Point.setX(..)) ;

7.2- Target & args

target: Это объект участвующий в JoinPoint (Точка объединения), объект вызывает method (или объект получает доступ к field).
args: Это параметр.

Посмотрим иллюстрированный пример:

AspectJ02.aj
package org.o7planning.tutorial.aspectj.demo02;

// Ready import Point class.
import figures.Point;

public aspect AspectJ02 {
 
    
    // Using target: define object on which the method is called
    // Using args: define args on the method is called
    // Using within: to restrict JoinPoint within ClassTest02
    pointcut callMove(Point point, int dx, int dy)  
                       : call(*  figures.Point.move(int,int))
                               && args(dx,dy) && target(point) && within(ClassTest02)  ;

    before(Point point, int dx, int dy) : callMove(point,  dx, dy )  {
        System.out.println("Before call move(" + dx + "," + dy + ")");
        System.out.println(point.toString());
    }
}
ClassTest02.java
package org.o7planning.tutorial.aspectj.demo02;

import figures.Point;

public class ClassTest02 {

  public static void main(String[] args) {

      Point point = new Point(10, 200);

      System.out.println("---- (1) ----");

      point.move(20, 30);

      System.out.println("---- (2) ----");

      System.out.println(point.toString());

      System.out.println("---- (3) ----");

      point.setX(100);
  }

}
Результаты запуска примера

7.3- Оператор && ||

AspectJ03.aj
package org.o7planning.tutorial.aspectj.demo03;


// Note: Must import FigureElement & Point
import figures.FigureElement;
import figures.Point;

public aspect AspectJ03 {
   
  // pointcut: Include move actions
   pointcut moveAction() :  (
           call(void FigureElement.move(int,int)) ||
           call(void Point.setX(int))              ||
           call(void Point.setY(int))                    
           )
           && within (ClassTest03);

   before() : moveAction()  {
       System.out.println("before move");
   }

}
ClassTest03.java
package org.o7planning.tutorial.aspectj.demo03;

import figures.FigureElement;
import figures.Line;
import figures.Point;

public class ClassTest03 {
  public static void main(String[] args) {

      Point point = new Point(10, 200);

      System.out.println("---- (1) ----");

      point.setX(20 );

      System.out.println("---- (2) ----");

      FigureElement line= new Line(new Point(1,1), new Point(10,10));
     
      line.move(10, 10);

      System.out.println("---- (3) ----");
  }
}
Результаты запуска примера

7.4- Field в AspectJ

FieldInAspectJ.aj
package org.o7planning.tutorial.aspectj.demo04;

import java.io.PrintStream;

public aspect FieldInAspectJ {

 // Field in AspectJ.
 PrintStream logStream = System.err;

 pointcut move() : call(* figures.Point.move(int,int)) && within(FieldInAspectJTest);

 before(): move() {
     logStream.println("Before Point move");
 }
}
FieldInAspectJTest.java
package org.o7planning.tutorial.aspectj.demo04;

import figures.Point;

public class FieldInAspectJTest {

  public static void main(String[] args) {

      Point point = new Point(10, 200);

      System.err.println("---- (1) ----");

      point.setX(20);

      System.err.println("---- (2) ----");

      point.move(10, 10);

      System.err.println("---- (3) ----");
  }
}
Результаты запуска примера:

7.5- Межтиповое объявление (Inter-type declarations)

PointObserving.aj
package org.o7planning.tutorial.aspectj.demo05;

import java.util.ArrayList;
import java.util.List;

import figures.Point;

public aspect PointObserving {

   // Class Point have no field: observers
   // However, it can declare here.
   // observers: Store the change point position.
   private List<Point> Point.observers = new ArrayList<Point>();

   pointcut moveAction(Point point) : call(void Point.move(int,int) )
                                  && target(point)
                                  && within(PointObservingTest);
   
   after(Point point) : moveAction(point)  {
       System.out.println("Point moved");
       // add new position
       point.observers.add(point);
       
       // Print the location of the point went through.
       System.out.println(" - "+point.observers);
   }

   public static void addObserver(Point p) {
       // p.observers.add(s);
   }

   public static void removeObserver(Point p) {
       // p.observers.remove(s);
   }
}
PointObservingTest.java
package org.o7planning.tutorial.aspectj.demo05;

import figures.Point;

public class PointObservingTest {

   public static void main(String[] args) {

       Point point1 = new Point(100, 100);

       // First move
       point1.move(10, 10);

       // Second move
       point1.move(10, 10);

       System.out.println("----------------------");

       Point point2 = new Point(200, 200);

       // First move
       point2.move(15, 10);

       // Second move
       point2.move(15, 10);

       // Third Move
       point2.move(25, 10);
   }
}
Результаты запуска примера:

7.6- Tracking

Tracking: То есть отслеживание(Какие методы были вызваны и в каком порядке, или определенные происшествия ...)
Мы создадим  Aspect, который определяет  pointcut содержащий участвующие pointcut где выполняются методы, и создаем так же Advice чтобы определить код, который будет выполнен в том месте.
SimpleTracing.aj
package org.o7planning.tutorial.aspectj.demo06;

import org.aspectj.lang.Signature;

public aspect SimpleTracing {


    // Collection of JoinPoint call any method
    // And within SimpleTracingTest
    pointcut tracedCall() : call (* *(..))
                    //  && !within(SimpleTracing)
                    && within(SimpleTracingTest)
                    ;

    before() : tracedCall()  {
        Signature sig = thisJoinPointStaticPart.getSignature();
        String line = ""
                + thisJoinPointStaticPart.getSourceLocation().getLine();

        String sourceName = thisJoinPointStaticPart.getSourceLocation()
                .getWithinType().getCanonicalName();
        //
        System.out.println("Call from " + sourceName + " line " + line + "\n   to "
                + sig.getDeclaringTypeName() + "." + sig.getName() +"\n");
    }

}
SimpleTracingTest.java
package org.o7planning.tutorial.aspectj.demo06;

import figures.Point;

public class SimpleTracingTest {

  private static void testMethod1() {
      Point point = new Point(100, 100);
      point.setX(100);
  }

  private static void testMethod2() {
     
      String text = "This is text";

      String s = text.substring(2);

      System.out.println(s);
  }

  public static void main(String[] args) {

      testMethod1();
     
      testMethod2();        
  }

}
Результаты запуска примера:

7.7- Cflow

Управление потоком (control flow) это поток выполнения программы внутри определнной точки объединения (JointPoint). cflow() и cflowbelow() созданы чтобы словить другие точки объединения как параметр и позволяет нам определить управление потоком на основании pointcut - эти pointcuts словят вся точки объединения в управлении потоком каждой определенной Joinpoint.
cflow() фиксирует все точки объединения (join point) как  call, execution, set и get field, error handlers в управлении потоком определенного  JointPont.
Изображение ниже содержит  JointPoint вызывая method MyClass.callA().
Используя  cflow(..):
Смотрите детальный пример:
CflowAspectJ.aj
package org.o7planning.tutorial.aspectj.demo07;

public aspect CflowAspectJ {

   pointcut call_cflow_callA() :  cflow( call( * MyClass.callA() ) )  && within(CFlowDemo || MyClass);

   before() : call_cflow_callA()  {
       System.out.println(
               "Join Point at: " + thisJoinPointStaticPart.getSourceLocation().getWithinType().getCanonicalName()
                       + " --> " + thisJoinPointStaticPart.getSourceLocation().getLine());
   }

}
MyClass.java
package org.o7planning.tutorial.aspectj.demo07;

public class MyClass {    

   public void callA() {

       callB();

       callC();
   }

   public void callB() {

       callC();
   }

   public void callC() {

   }
   
}
CFlowDemo.java
package org.o7planning.tutorial.aspectj.demo07;

public class CFlowDemo {

   public static void main(String[] args) {


       MyClass myClass= new MyClass();
       
       myClass.callA();
       
       myClass.callA();

   }

}
Результат запуска class CFlowDemo:

7.8- Cflowbelow

Управление потоком (control flow) это поток выполнения программы внутри определенной точки объединения (JointPoint). cflow() и cflowbelow() созданы чтобы словить другие точки объединения, как параметр и позволяет нам определить управление потоком основываясь на pointcut - эти pointcuts словят все точки объединения в управлении потоком каждой определенной Joinpoint.
cflowbelow() имеет поведение схожее с  cflow, но он не ловит точки объединения в его параметрах, и ловит все остальные точки объединения.
Вы можете посмотреть больше в части  cflow().