Аспектно-ориентированное программирование Java с помощью AspectJ (AOP)
View more Tutorials:
....
Данная статья основана на:
-
Eclipse 4.4 (LUNA)
-
AspectJ 1.8.2
Вам необходимо установить инструмент разработки AspectJ в Eclipse, вы можете ознакомиться с инструкциями по ссылке:
В данной статье я использую некоторые классы, которые участвуют в иллюстрированном примере 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); } }
Для начала создайте обычный проект с названием AspectJTutorial.

Кликните правой кнопкой мыши на Project и выберите Configure/Convert to AspectJ Project.

Convert завершен:

Для начала, мы используем первый пример, прежде чем начинать с понятиями:
Создайте класс 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());
В AspectJ вам нужно различать несколько понятий:
- Advice
- Pointcut
- JoinPoint
JoinPoint (точка объединения) является четко определенной точкой в потоке программы.
- Мы хотим выполнить определенный код ("advice") при каждом достижении точки объединения
- Мы не хотим загромождать код явными индикаторами, говорящими: "Это точка объединения",
- То есть не вписываем код сами в это место
- AspectJ предоставляет синтаксис для указания этих JoinPoint "извне" фактического кода.
Отметить их и выполнить определенный код извне.
JoinPoint - это точка в программном потоке "Где происходит что-то определенное", что-то здесь может быть:
- Когда метод вызывается
- Когда выбрасывается исключение
- Когда получен доступ (accessed)
- При инициализации объекта
- При ссылке к объекту
Таким образом JoinPoint очень разнообразен, точка где вы инициализируете объект тоже рассматривается как JoinPoint.
Возвращаемся к примеру HelloWorld, мы определим некоторые JoinPoints:

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

Возвращаемся к примеру 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"); }
Прежде чем перейти к повышенным деталям, мы рассмотрим базовые примеры и правила в AspectJ, если вы не поймете эти правила, вы не сможете понять AspectJ.
В 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(..)) ;
target: Это объект участвующий в JoinPoint (Точка объединения), объект вызывает method (или объект получает доступ к field).
args: Это параметр.
Посмотрим иллюстрированный пример:
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); } }

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

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) ----"); } }
Результаты запуска примера

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) ----"); } }
Результаты запуска примера:

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); } }
Результаты запуска примера:

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(); } }
Результаты запуска примера:

Управление потоком (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:

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