openplanning

Lập trình Java hướng khía cạnh với AspectJ (AOP)

  1. Giới thiệu
  2. Các yêu cầu trước khi bắt đầu
  3. Các lớp tham gia vào các ví dụ
  4. Tạo AspectJ Project
  5. Ví dụ đầu tiên bắt đầu với AspectJ
  6. Các khái niệm cơ bản trong AspectJ
  7. Ví dụ AspectJ cơ bản

TÀI LIỆU ĐANG ĐƯỢC VIẾT 50%.

1. Giới thiệu

Tài liệu này được viết dựa trên:
  • Eclipse 4.4 (LUNA)

  • AspectJ 1.8.2

2. Các yêu cầu trước khi bắt đầu

Bạn cần cài đặt công cụ phát triển AspectJ vào Eclipse, bạn có thể xem hướng dẫn tại:

3. Các lớp tham gia vào các ví dụ

Trong tài liệu này tôi sử dụng một số class, nó tham gia vào nhiều ví dụ minh họa AspectJ.
  • Box
  • FigureElement
  • Group
  • Line
  • Point
  • ShapeFigureElement
  • SlotfulPoint
Nguồn các class này lấy tại:
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. Tạo AspectJ Project

Trước hết tạo một Project thông thường, lấy tên là AspectJTutorial.
Nhấn phải chuột vào Project, chọn Configure/Convert to AspectJ Project.
Convert thành công:

5. Ví dụ đầu tiên bắt đầu với AspectJ

Trước hết chúng ta làm một ví dụ đầu tiên, trước khi bắt đầu với các khái niệm:
Tạo class 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();
     
     
  }

}
Một class như trên là hoàn toàn không có gì đáng chú ý, tuy nhiên đặt ra vấn đề bạn muốn chương trình làm một cái gì đó ngay trước, hoặc ngay sau khi phương thức sayHello() được gọi, chẳng hạn in ra màn hình câu thông báo. Theo cách truyền thống bạn sẽ thêm vào một dòng lệnh in ra màn hình ngay trước khi gọi sayHello().
AspectJ là một hướng lập trình khía cạnh (aspect programming), nó cho bạn một giải pháp khác để giải quyết vấn đề này, tất nhiên AspectJ còn làm được nhiều hơn thế. Hãy nhớ đây chỉ là ví dụ HelloWorld.
  • File/New/Other..
Một file có tên: HelloAspectJ.aj đã được tạo ra, nó có cấu trúc khá giống với một class.
AspectJ các phiên bản trước kia sử dụng Annotation để mô tả, các phiên bản gần đây mới đưa vào file *.aj. Trong tài liệu này tôi bỏ qua không sử dụng Annotation, vì nó không rõ ràng bằng việc sử dụng file *.aj. Ngoài ra file *.aj có ngữ pháp giống một class, eclipse sẽ thông báo giúp bạn ngữ pháp viết sai.
Sửa code của HelloAspectJ.aj:
HelloAspectJ.aj
package org.o7planning.tutorial.aspectj.helloaspectj;

public aspect HelloAspectJ {

 // Định nghĩa ra một pointcut.
 // call: gọi method sayHello() của class HelloAspectJDemo
 pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());

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

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

}

AspectJ có chút khác biệt với một class thông thường nó thêm vào rất nhiều các từ khóa. Và file có đuôi là *.aj chứ không phải là *.java. Một số từ khóa của 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
Quay trở lại với ví dụ HelloWorld. Bây giờ chúng ta chạy class HelloAspectJDemo. Và kết quả nhận được:
Before call sayHello
Hello
After call sayHello
--------
Before call sayHello
Hello
After call sayHello
--------
Before call sayHello
Hello
After call sayHello
John
AspectJ là vô cùng mạnh mẽ. Chẳng hạn với ví dụ trên nếu bạn sửa lại code trên HelloAspectJ.aj một chút có thể làm cho nó có tác dụng trên mọi class gọi hàm sayHello().
// Định nghĩa ra một pointcut.
// call: gọi method sayHello() của class HelloAspectJDemo
pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());


// Sửa lại thành:
// Có nghĩa là mọi nơi (class, aspectj) gọi sayHello()
pointcut callSayHello(): call(* *.sayHello());

6. Các khái niệm cơ bản trong AspectJ

Trong AspectJ bạn cần phân biệt một số khái niệm:
  • Advice
  • Pointcut
  • JoinPoint
JoinPoint
JoinPoint (Điểm tham gia) là một điểm được xác định rõ ràng trong dòng chảy của chương trình
  • Chúng ta muốn thi hành một đoạn code (“advice”) mỗi lần gặp điểm JoinPont.
  • Chúng ta không muốn làm lộn xộn code bằng việc chỉ rõ ràng “Đây là các điểm tham gia”
    • Nghĩa là không tự viết code vào các vị trí đó.
  • AspectJ cung cấp một cú pháp để chỉ ra những JoinPoint "từ bên ngoài" mã thực tế.
    • Đánh dấu chúng lại rồi thực thi code nào đó viết bên ngoài.
JoinPoint là một điểm trong dòng chảy chương trình "Nơi một cái gì đó sẽ xảy ra", cái gì ở đây có thể là:
  • Nơi method được gọi
  • Nơi một ngoại lệ đã ném ra
  • Nơi biến được truy cập (accessed)
  • Nơi khởi tạo một đối tượng
  • Nơi tham chiếu tới một đối tượng.
Như vậy JoinPoint rất đa dạng, vị trí bạn khởi tạo một đối tượng thì đó cũng coi là một JoinPoint.
Quay lại với ví dụ HelloWorld chúng ta sẽ xác định một số JoinPoint:
PointCut
Định nghĩa Pointcut bao gồm một bên trái và một bên phải, cách nhau bằng dấu hai chấm
  • Phía bên trái bao gồm các tên pointcut và các tham số pointcut (chẳng hạn, các dữ liệu có sẵn khi các sự kiện xảy ra)
  • Bên phải chứa bản thân Pointcut
Ví dụ:
pointcut callSayHello(): call(* HelloAspectJDemo.sayHello());
  • Tên của Pointcut là callSayHello
  • Pointcut này không có tham số
  • Bản thân Pointcut là call(* HelloAspectJDemo.sayHello())
  • Pointcut này đề cập đến bất kỳ thời điểm nào phương thức HelloAspectJDemo.sayHello() được gọi.
Advice
Quay trở lại với ví dụ HelloWorld, chúng ta có 2 advice
  • before()
  • after()
Advice (Tư vấn hoặc lời khuyên) xác định một hành vi. Các đoạn code của một Advice sẽ được chạy ở mọi điểm tham gia (Join point)của Pointcut. Chính xác cách thức mã này được chạy phụ thuộc vào loại Advice (loại tư vấn).
AspectJ hỗ trợ ba loại Advice. Các loại Advice xác định nó tương tác như thế nào với các JoinPoint (điểm tham gia) nó được định nghĩa. Vì vậy AspectJ chia ra before (chạy trước) JoinPoint, after (chạy sau) JoinPoint, và around (chạy tại chỗ hoặc "xung quanh") JoinPoint.
Trong khi "before Advice" là tương đối không có vấn đề, với "after Advice" có 3 tình huống: Sau khi thực hiện của một JoinPoint hoàn thành bình thường, sau khi ném một ngoại lệ, hoặc sau khi thực hiện một trong hai. AspectJ cho phép "after Advice" cho bất kỳ hai tình huống này.
  • TODO: Giải thích thêm "around Advice".
Để dễ hiểu chúng ta xem một vài ví dụ với Advice:
HelloAspectJ2.aj
package org.o7planning.tutorial.aspectj.helloaspectj;

public aspect HelloAspectJ2 {

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

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

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

  // Bao gồm cả 2 tình huống trả về bình thường
  // Hoặc bị ngoại lệ khi gọi method (sayHello)
  after() : callSayHello()  {
      System.out.println("Returned or threw an Exception");
  }
}
Loại Advice "after returning" có thể không cần quan tâm tới giá trị trả về (của method) lúc đó có có thể viết.
// Advice loại "after returning".
// Quan tâm tới giá trị trả về
after() returning (Object retObj): callSayHello() {
  System.out.println("Returned normally with " + retObj);
}

// Advice loại "after returning" không cần quan tâm tới Object trả về.
after() returning(): callSayHello() {
  System.out.println("Returned normally");
}

// Hoặc
// Advice loại "after returning" không cần quan tâm tới Object trả về.
after() returning : callSayHello() {
  System.out.println("Returned normally");
}

7. Ví dụ AspectJ cơ bản

Trước khi đi vào chi tiết nâng cao, chúng ta xem các ví dụ cơ bản, và các quy tắc trong AspectJ, nếu không hiểu được các quy tắc này bạn không thể hiểu được AspectJ.
Quy tắc mô tả class, method, * và ..
Trong AspectJ nếu bạn muốn nhắc tới một lớp, một phương thức trong khai báo Pointcut bạn cần phải ghi tên đầy đủ của nó. Ví dụ:
  • figures.Point
  • figures.Point.setX(int)
Trường hợp class và AspectJ cùng trong một package thì bạn có thể ghi một cách ngắn gọn. Ví dụ:
  • Point
  • Point.setX(int)
Hãy xem một ví dụ minh họa dưới đây, AspectJ01.aj và class Point nằm trong 2 package khác nhau.
AspectJ01.aj
package org.o7planning.tutorial.aspectj.demo01;

public aspect AspectJ01 {

  // Class Point và AspectJ này không cùng package
  // vì vậy phải ghi rõ cả package (Bắt buộc).
  // Pointcut này định nghĩa các điểm tham gia (JoinPoint)
  // chỉ trong phạm vi class ClassTest01
  // ClassTest01 và AspectJ này cùng package,
  // vì vậy có thể bỏ qua package trong 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) ----");
  }

}
Kết quả chạy lớp ClassTest01:
---- (1) ----
Before call Point.setX(int)
---- (2) ----
---- (3) ----
Như vậy tại đây có một số ghi chú:
// within sử dụng để hạn chế phạm vi của Pointcut
// trong trường hợp dưới đây
// Chỉ chứa các điểm tham gia (JoinPoint) trong class ClassTest01.
within (ClassTest01)

// Có thể sử dụng import
import figures.Point;
.....
// Và có thể viết ngắn gọn.
pointcut callSetX()  : call(void  Point.setX(int)) && within (ClassTest01) ;
Dấu * trong AspectJ
// Tập hợp các JoinPont gọi method Point.setX(int) với package bất kỳ.
pointcut callSetX()  : call(void  *.Point.setX(int)) ;

// Tập hợp các Pointcut gọi Point.setX(int) với package bất kỳ
// và kiểu trả về bất kỳ.
pointcut callSetX()  : call(* *.Point.setX(int)) ;


// Pointcut gọi các method tĩnh setX(int) của một class có tên *Point (APoint, AbcPoint,...)
// và thuộc package sample, kiểu trả về int.
pointcut callSetX()  : call(public static int sample.*Point.setX(int)) ;


// Sử dụng (..) để mô tả method có 0 hoặc nhiều tham số.
pointcut callSetX()  : call(public static int sample.*Point.setX(..)) ;
Target & args
target: Là đối tượng tham gia tại JoinPoint (Điểm tham gia), đối tượng gọi method(hoặc đối tượng truy cập field).
args: Là tham số.

Hãy xem ví dụ minh họa:
AspectJ02.aj
package org.o7planning.tutorial.aspectj.demo02;

// Đã import class Point.
import figures.Point;

public aspect AspectJ02 {

   // Pointcut, khi gọi Point.move(int,int)
   // Sử dụng target để khai báo đối tượng tham gia
   // Sử dụng args để khai báo các tham số.
   // within để hạn chế các JoinPoint chỉ trong class 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);
  }

}
Kết quả chạy ví dụ
---- (1) ----
Before call move(20,30)
Point(10, 200)
---- (2) ----
Point(30,230)
---- (3) ----
Toán tử && ||
AspectJ03.aj
package org.o7planning.tutorial.aspectj.demo03;


// Chú ý: Phải import FigureElement & Point
import figures.FigureElement;
import figures.Point;

public aspect AspectJ03 {
 
  // Pointcut gồm các hành động di chuyển (move)
  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) ----");
  }
}
Kết quả chạy ví dụ
---- (1) ----
before move
---- (2) ----
before move
---- (3) ----
Field trong 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) ----");
  }
}
Kết quả chạy ví dụ:
---- (1) ----
---- (2) ----
Before Point move
---- (3) ----
Khai báo kiểu nội bộ (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 hoàn toàn không có field: observers
  // Tuy nhiên vẫn có thể khai báo tại đây.
  // observers: Ghi lại các vị trí điểm thay đổi.
  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");
      // Thêm vào vị trí mới.
      point.observers.add(point);
     
      // Ghi ra các vị trí của điểm đã đi qua.
      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);

      // Di chuyển lần 1
      point1.move(10, 10);

      // Di chuyển lần 2
      point1.move(10, 10);

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

      Point point2 = new Point(200, 200);

      // Di chuyển lần 1
      point2.move(15, 10);

      // Di chuyển lần 2
      point2.move(15, 10);

      // Di chuyển lần 3
      point2.move(25, 10);
  }
}
Kết quả chạy ví dụ:
Point moved
 - [Point(110, 110)]
Point moved
  - [Point(120, 120), Point(120, 120)]
----------------------
Point moved
 - [Point(215, 210)]
Point moved
  - [Point(230, 220), Point(230, 220)]  
Point moved
  - [Point(255, 230), Point(255, 230), Point(255, 230)]
Tracking
Tracking: Nghĩa là theo dấu vết (Các phương thức nào đã được gọi và lần lượt thế nào, hoặc diễn biến của cái gì đó ...)
Chúng ta sẽ tạo một Aspect, nó định nghĩa một pointcut chứa các điểm tham gia là các nơi mà phương thức thực thi, và cũng tạo một Advice để định nghĩa đoạn code sẽ được thực thi tại các vị trí đó.
SimpleTracing.aj
package org.o7planning.tutorial.aspectj.demo06;

import org.aspectj.lang.Signature;

public aspect SimpleTracing {

 // Tập hợp các JoinPoint tại vị trí gọi method bất kỳ.
 // và nằm trong class 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();        
  }

}
Kết quả chạy ví dụ:
Call from org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest line 23
  to org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest.testMethod1

Call from org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest line 9
  to figures.Point.setX

Call from org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest line 25
  to org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest.testMethod2

Call from org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest line 16
  to java.lang.String.substring

Call from org.o7planning.tutorial.aspectj.demo06.SimpleTracingTest line 18
  to java.io.PrintStream.println

is is text
Ví dụ trên tôi giới hạn việc ghi ra thông tin các method được gọi bên trong code của class SimpleTrackingTest. Nếu bạn bỏ điều kiện để loại bỏ các JoinPoint trên chính aspect SimpleTracking, điều này là cần thiết để tránh một vòng lặp vô tận.
Cflow
Luồng điều khiển (control flow) là luồng các điểm thực hiện của chương trình bên trong của một điểm tham gia (JointPoint) cụ thể. cflow()cflowbelow() được tạo ra để tóm lấy các điểm tham gia khác như là một thông số và cho phép chúng ta định nghĩa một luồng điều khiển dựa trên pointcut - pointcuts này sẽ tóm tất cả các điểm tham gia trong luồng điều khiển của từng Joinpoint cụ thể.
cflow() sẽ tóm lấy tất cả các điểm tham gia (join point) như call, execution, set get field, error handlers trong luồng điều khiển của một JointPont cụ thể.
Hình minh họa dưới đây chứa các JointPoint gọi phương thức MyClass.callA().
Sử dụng cflow(..):
Xem ví dụ chi tiết:
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();

   }

}
Kết quả chạy class CFlowDemo:
Join Point at: org.o7planning.tutorial.aspectj.demo07.CFlowDemo --> 10
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 5
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 7
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 12
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 14
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 17
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 9
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 17
Join Point at: org.o7planning.tutorial.aspectj.demo07.CFlowDemo --> 12
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 5
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 7
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 12
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 14
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 17
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 9
Join Point at: org.o7planning.tutorial.aspectj.demo07.MyClass --> 17
Cflowbelow
Luồng điều khiển (control flow) là luồng các điểm thực hiện của chương trình bên trong của một điểm tham gia (JointPoint) cụ thể. cflow()cflowbelow() được tạo ra để tóm lấy các điểm tham gia khác như là một thông số và cho phép chúng ta định nghĩa một luồng điều khiển dựa trên pointcut - pointcuts này sẽ tóm tất cả các điểm tham gia trong luồng điều khiển của từng Joinpoint cụ thể.
cflowbelow() có hành vi giống với cflow, nhưng nó không tóm điểm tham gia trong tham số của nó, và tóm tất cả các điểm tham gia còn lại.
Bạn có thể xem thêm ở mục cflow().

Java cơ bản

Show More