1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918 |
- [[examples]]
- = Examples
-
- [[examples-intro]]
- == Introduction
-
- This chapter consists entirely of examples of AspectJ use.
-
- The examples can be grouped into four categories:
-
- technique::
- Examples which illustrate how to use one or more features of the language
- development::
- Examples of using AspectJ during the development phase of a project
- production::
- Examples of using AspectJ to provide functionality in an application
- reusable::
- Examples of reuse of aspects and pointcuts
-
- [[examples-howto]]
- == Obtaining, Compiling and Running the Examples
-
- The examples source code is part of the AspectJ distribution which may
- be downloaded from the https://eclipse.org/aspectj[AspectJ project page].
-
- Compiling most examples is straightforward. Go the `InstallDir/examples`
- directory, and look for a `.lst` file in one of the example
- subdirectories. Use the `-arglist` option to `ajc` to compile the
- example. For instance, to compile the telecom example with billing, type
-
- [source, text]
- ....
- ajc -argfile telecom/billing.lst
- ....
-
- To run the examples, your classpath must include the AspectJ run-time
- Java archive (`aspectjrt.jar`). You may either set the `CLASSPATH`
- environment variable or use the `-classpath` command line option to the
- Java interpreter:
-
- [source, text]
- ....
- (In Unix use a : in the CLASSPATH)
- java -classpath ".:InstallDir/lib/aspectjrt.jar" telecom.billingSimulation
- ....
-
- [source, text]
- ....
- (In Windows use a ; in the CLASSPATH)
- java -classpath ".;InstallDir/lib/aspectjrt.jar" telecom.billingSimulation
- ....
-
- [[examples-basic]]
- == Basic Techniques
-
- This section presents two basic techniques of using AspectJ, one each
- from the two fundamental ways of capturing crosscutting concerns: with
- dynamic join points and advice, and with static introduction. Advice
- changes an application's behavior. Introduction changes both an
- application's behavior and its structure.
-
- The first example, xref:#examples-joinPoints[Join Points and `thisJoinPoint`], is about
- gathering and using information about the join point that has triggered
- some advice. The second example, xref:#examples-roles[Roles and Views],
- concerns a crosscutting view of an existing class hierarchy.
-
- [[examples-joinPoints]]
- === Join Points and `thisJoinPoint`
-
- (The code for this example is in `InstallDir/examples/tjp`.)
-
- A join point is some point in the execution of a program together with a
- view into the execution context when that point occurs. Join points are
- picked out by pointcuts. When a program reaches a join point, advice on
- that join point may run in addition to (or instead of) the join point
- itself.
-
- When using a pointcut that picks out join points of a single kind by
- name, typicaly the the advice will know exactly what kind of join point
- it is associated with. The pointcut may even publish context about the
- join point. Here, for example, since the only join points picked out by
- the pointcut are calls of a certain method, we can get the target value
- and one of the argument values of the method calls directly.
-
- [source, java]
- ....
- before(Point p, int x):
- target(p) &&
- args(x) &&
- call(void setX(int))
- {
- if (!p.assertX(x))
- System.out.println("Illegal value for x"); return;
- }
- ....
-
- But sometimes the shape of the join point is not so clear. For instance,
- suppose a complex application is being debugged, and we want to trace
- when any method of some class is executed. The pointcut
-
- [source, java]
- ....
- pointcut execsInProblemClass():
- within(ProblemClass) &&
- execution(* *(..));
- ....
-
- will pick out each execution join point of every method defined within
- `ProblemClass`. Since advice executes at each join point picked out by
- the pointcut, we can reasonably ask which join point was reached.
-
- Information about the join point that was matched is available to advice
- through the special variable `thisJoinPoint`, of type
- xref:../runtime-api/org/aspectj/lang/JoinPoint.html[`org.aspectj.lang.JoinPoint`].
- Through this object we can access information such as
-
- * the kind of join point that was matched
- * the source location of the code associated with the join point
- * normal, short and long string representations of the current join
- point
- * the actual argument values of the join point
- * the signature of the member associated with the join point
- * the currently executing object
- * the target object
- * an object encapsulating the static information about the join point.
- This is also available through the special variable `thisJoinPointStaticPart`.
-
- ==== The `Demo` class
-
- The class `tjp.Demo` in `tjp/Demo.java` defines two methods `foo` and
- `bar` with different parameter lists and return types. Both are called,
- with suitable arguments, by ``Demo``'s `go` method which was invoked from
- within its `main` method.
-
- [source, java]
- ....
- public class Demo {
- static Demo d;
-
- public static void main(String[] args) {
- new Demo().go();
- }
-
- void go() {
- d = new Demo();
- d.foo(1,d);
- System.out.println(d.bar(new Integer(3)));
- }
-
- void foo(int i, Object o) {
- System.out.println("Demo.foo(" + i + ", " + o + ")\n");
- }
-
- String bar (Integer j) {
- System.out.println("Demo.bar(" + j + ")\n");
- return "Demo.bar(" + j + ")";
- }
- }
- ....
-
- ==== The `GetInfo` aspect
-
- This aspect uses around advice to intercept the execution of methods
- `foo` and `bar` in `Demo`, and prints out information garnered from
- `thisJoinPoint` to the console.
-
- [source, java]
- ....
- aspect GetInfo {
-
- static final void println(String s){ System.out.println(s); }
-
- pointcut goCut(): cflow(this(Demo) && execution(void go()));
-
- pointcut demoExecs(): within(Demo) && execution(* *(..));
-
- Object around(): demoExecs() && !execution(* go()) && goCut() {
- println("Intercepted message: " +
- thisJoinPointStaticPart.getSignature().getName());
- println("in class: " +
- thisJoinPointStaticPart.getSignature().getDeclaringType().getName());
- printParameters(thisJoinPoint);
- println("Running original method: \n" );
- Object result = proceed();
- println(" result: " + result );
- return result;
- }
-
- static private void printParameters(JoinPoint jp) {
- println("Arguments: " );
- Object[] args = jp.getArgs();
- String[] names = ((CodeSignature)jp.getSignature()).getParameterNames();
- Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes();
- for (int i = 0; i < args.length; i++) {
- println(
- " " + i + ". " + names[i] +
- " : " + types[i].getName() +
- " = " + args[i]);
- }
- }
- }
- ....
-
- ===== Defining the scope of a pointcut
-
- The pointcut `goCut` is defined as
-
- [source, java]
- ....
- cflow(this(Demo)) && execution(void go())
- ....
-
- so that only executions made in the control flow of `Demo.go` are
- intercepted. The control flow from the method `go` includes the
- execution of `go` itself, so the definition of the around advice
- includes `!execution(* go())` to exclude it from the set of executions
- advised.
-
- ===== Printing the class and method name
-
- The name of the method and that method's defining class are available as
- parts of the
- xref:../runtime-api/org/aspectj/lang/Signature.html[`org.aspectj.lang.Signature`]
- object returned by calling `getSignature()` on either `thisJoinPoint` or
- `thisJoinPointStaticPart`.
-
- ===== Printing the parameters
-
- The static portions of the parameter details, the name and types of the
- parameters, can be accessed through the
- xref:../runtime-api/org/aspectj/lang/reflect/CodeSignature.html[`org.aspectj.lang.reflect.CodeSignature`]
- associated with the join point. All execution join points have code
- signatures, so the cast to `CodeSignature` cannot fail.
-
- The dynamic portions of the parameter details, the actual values of the
- parameters, are accessed directly from the execution join point object.
-
- [[examples-roles]]
- === Roles and Views
-
- (The code for this example is in `InstallDir/examples/introduction`.)
-
- Like advice, inter-type declarations are members of an aspect. They
- declare members that act as if they were defined on another class.
- Unlike advice, inter-type declarations affect not only the behavior of
- the application, but also the structural relationship between an
- application's classes.
-
- This is crucial: Publically affecting the class structure of an
- application makes these modifications available to other components of
- the application.
-
- Aspects can declare inter-type
-
- * fields
- * methods
- * constructors
-
- and can also declare that target types
-
- * implement new interfaces
- * extend new classes
-
- This example provides three illustrations of the use of inter-type
- declarations to encapsulate roles or views of a class. The class our
- aspect will be dealing with, `Point`, is a simple class with rectangular
- and polar coordinates. Our inter-type declarations will make the class
- `Point`, in turn, cloneable, hashable, and comparable. These facilities
- are provided by AspectJ without having to modify the code for the class
- `Point`.
-
- ==== The `Point` class
-
- The `Point` class defines geometric points whose interface includes
- polar and rectangular coordinates, plus some simple operations to
- relocate points. ``Point``'s implementation has attributes for both its
- polar and rectangular coordinates, plus flags to indicate which
- currently reflect the position of the point. Some operations cause the
- polar coordinates to be updated from the rectangular, and some have the
- opposite effect. This implementation, which is in intended to give the
- minimum number of conversions between coordinate systems, has the
- property that not all the attributes stored in a `Point` object are
- necessary to give a canonical representation such as might be used for
- storing, comparing, cloning or making hash codes from points. Thus the
- aspects, though simple, are not totally trivial.
-
- The diagram below gives an overview of the aspects and their interaction
- with the class `Point`.
-
- image:images/aspects.png[image]
-
- ==== The `CloneablePoint` aspect
-
- This first aspect is responsible for ``Point``'s implementation of the
- `Cloneable` interface. It declares that `Point implements Cloneable`
- with a `declare parents` form, and also publically declares a
- specialized ``Point``'s `clone()` method. In Java, all objects inherit the
- method `clone` from the class `Object`, but an object is not cloneable
- unless its class also implements the interface `Cloneable`. In addition,
- classes frequently have requirements over and above the simple
- bit-for-bit copying that `Object.clone` does. In our case, we want to
- update a ``Point``'s coordinate systems before we actually clone the
- `Point`. So our aspect makes sure that `Point` overrides `Object.clone`
- with a new method that does what we want.
-
- We also define a test `main` method in the aspect for convenience.
-
- [source, java]
- ....
- public aspect CloneablePoint {
-
- declare parents: Point implements Cloneable;
-
- public Object Point.clone() throws CloneNotSupportedException {
- // we choose to bring all fields up to date before cloning.
- makeRectangular();
- makePolar();
- return super.clone();
- }
-
- public static void main(String[] args) {
- Point p1 = new Point();
- Point p2 = null;
-
- p1.setPolar(Math.PI, 1.0);
- try {
- p2 = (Point)p1.clone();
- } catch (CloneNotSupportedException e) {}
- System.out.println("p1 =" + p1);
- System.out.println("p2 =" + p2);
-
- p1.rotate(Math.PI / -2);
- System.out.println("p1 =" + p1);
- System.out.println("p2 =" + p2);
- }
- }
- ....
-
- ==== The `ComparablePoint` aspect
-
- `ComparablePoint` is responsible for ``Point``'s implementation of the
- `Comparable` interface.
-
- The interface `Comparable` defines the single method `compareTo` which
- can be use to define a natural ordering relation among the objects of a
- class that implement it.
-
- `ComparablePoint` uses `declare parents` to declare that `Point implements Comparable`,
- and also publically declares the appropriate `compareTo(Object)` method:
- A `Point` `p1` is said to be less than another `Point p2` if `p1` is closer to the origin.
-
- We also define a test `main` method in the aspect for convenience.
-
- [source, java]
- ....
- public aspect ComparablePoint {
-
- declare parents: Point implements Comparable;
-
- public int Point.compareTo(Object o) {
- return (int) (this.getRho() - ((Point)o).getRho());
- }
-
- public static void main(String[] args) {
- Point p1 = new Point();
- Point p2 = new Point();
-
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
-
- p1.setRectangular(2,5);
- p2.setRectangular(2,5);
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
-
- p2.setRectangular(3,6);
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
-
- p1.setPolar(Math.PI, 4);
- p2.setPolar(Math.PI, 4);
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
-
- p1.rotate(Math.PI / 4.0);
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
-
- p1.offset(1,1);
- System.out.println("p1 =?= p2 :" + p1.compareTo(p2));
- }
- }
- ....
-
- ==== The `HashablePoint` aspect
-
- Our third aspect is responsible for ``Point``'s overriding of ``Object``'s
- `equals` and `hashCode` methods in order to make ``Point``s hashable.
-
- The method `Object.hashCode` returns an integer, suitable for use as a
- hash table key. It is not required that two objects which are not equal
- (according to the `equals` method) return different integer results from
- `hashCode` but it can improve performance when the integer is used as a
- key into a data structure. However, any two objects which are equal must
- return the same integer value from a call to `hashCode`. Since the
- default implementation of `Object.equals` returns `true` only when two
- objects are identical, we need to redefine both `equals` and `hashCode`
- to work correctly with objects of type `Point`. For example, we want two
- `Point` objects to test equal when they have the same `x` and `y`
- values, or the same `rho` and `theta` values, not just when they refer
- to the same object. We do this by overriding the methods `equals` and
- `hashCode` in the class `Point`.
-
- So `HashablePoint` declares ``Point``'s `hashCode` and `equals` methods,
- using ``Point``'s rectangular coordinates to generate a hash code and to
- test for equality. The `x` and `y` coordinates are obtained using the
- appropriate get methods, which ensure the rectangular coordinates are
- up-to-date before returning their values.
-
- And again, we supply a `main` method in the aspect for testing.
-
- [source, java]
- ....
- public aspect HashablePoint {
-
- public int Point.hashCode() {
- return (int) (getX() + getY() % Integer.MAX_VALUE);
- }
-
- public boolean Point.equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof Point)) return false;
- Point other = (Point)o;
- return (getX() == other.getX()) && (getY() == other.getY());
- }
-
- public static void main(String[] args) {
- Hashtable h = new Hashtable();
- Point p1 = new Point();
-
- p1.setRectangular(10, 10);
- Point p2 = new Point();
-
- p2.setRectangular(10, 10);
-
- System.out.println("p1 = " + p1);
- System.out.println("p2 = " + p2);
- System.out.println("p1.hashCode() = " + p1.hashCode());
- System.out.println("p2.hashCode() = " + p2.hashCode());
-
- h.put(p1, "P1");
- System.out.println("Got: " + h.get(p2));
- }
- }
- ....
-
- [[examples-development]]
- == Development Aspects
-
- === Tracing using aspects
-
- (The code for this example is in `InstallDir/examples/tracing`.)
-
- Writing a class that provides tracing functionality is easy: a couple of
- functions, a boolean flag for turning tracing on and off, a choice for
- an output stream, maybe some code for formatting the output -- these are
- all elements that `Trace` classes have been known to have. `Trace`
- classes may be highly sophisticated, too, if the task of tracing the
- execution of a program demands it.
-
- But developing the support for tracing is just one part of the effort of
- inserting tracing into a program, and, most likely, not the biggest
- part. The other part of the effort is calling the tracing functions at
- appropriate times. In large systems, this interaction with the tracing
- support can be overwhelming. Plus, tracing is one of those things that
- slows the system down, so these calls should often be pulled out of the
- system before the product is shipped. For these reasons, it is not
- unusual for developers to write ad-hoc scripting programs that rewrite
- the source code by inserting/deleting trace calls before and after the
- method bodies.
-
- AspectJ can be used for some of these tracing concerns in a less ad-hoc
- way. Tracing can be seen as a concern that crosscuts the entire system
- and as such is amenable to encapsulation in an aspect. In addition, it
- is fairly independent of what the system is doing. Therefore tracing is
- one of those kind of system aspects that can potentially be plugged in
- and unplugged without any side-effects in the basic functionality of the
- system.
-
- ==== An Example Application
-
- Throughout this example we will use a simple application that contains
- only four classes. The application is about shapes. The `TwoDShape`
- class is the root of the shape hierarchy:
-
- [source, java]
- ....
- public abstract class TwoDShape {
- protected double x, y;
- protected TwoDShape(double x, double y) {
- this.x = x; this.y = y;
- }
- public double getX() { return x; }
- public double getY() { return y; }
- public double distance(TwoDShape s) {
- double dx = Math.abs(s.getX() - x);
- double dy = Math.abs(s.getY() - y);
- return Math.sqrt(dx*dx + dy*dy);
- }
- public abstract double perimeter();
- public abstract double area();
- public String toString() {
- return (" @ (" + String.valueOf(x) + ", " + String.valueOf(y) + ") ");
- }
- }
- ....
-
- `TwoDShape` has two subclasses, `Circle` and `Square`:
-
- [source, java]
- ....
- public class Circle extends TwoDShape {
- protected double r;
- public Circle(double x, double y, double r) {
- super(x, y); this.r = r;
- }
- public Circle(double x, double y) { this( x, y, 1.0); }
- public Circle(double r) { this(0.0, 0.0, r); }
- public Circle() { this(0.0, 0.0, 1.0); }
- public double perimeter() {
- return 2 * Math.PI * r;
- }
- public double area() {
- return Math.PI * r*r;
- }
- public String toString() {
- return ("Circle radius = " + String.valueOf(r) + super.toString());
- }
- }
- ....
-
- [source, java]
- ....
- public class Square extends TwoDShape {
- protected double s; // side
- public Square(double x, double y, double s) {
- super(x, y); this.s = s;
- }
- public Square(double x, double y) { this( x, y, 1.0); }
- public Square(double s) { this(0.0, 0.0, s); }
- public Square() { this(0.0, 0.0, 1.0); }
- public double perimeter() {
- return 4 * s;
- }
- public double area() {
- return s*s;
- }
- public String toString() {
- return ("Square side = " + String.valueOf(s) + super.toString());
- }
- }
- ....
-
- To run this application, compile the classes. You can do it with or
- without ajc, the AspectJ compiler. If you've installed AspectJ, go to
- the directory `InstallDir/examples` and type:
-
- [source, text]
- ....
- ajc -argfile tracing/notrace.lst
- ....
-
- To run the program, type
-
- [source, text]
- ....
- java tracing.ExampleMain
- ....
-
- (we don't need anything special on the classpath since this is pure Java
- code). You should see the following output:
-
- [source, text]
- ....
- c1.perimeter() = 12.566370614359172
- c1.area() = 12.566370614359172
- s1.perimeter() = 4.0
- s1.area() = 1.0
- c2.distance(c1) = 4.242640687119285
- s1.distance(c1) = 2.23606797749979
- s1.toString(): Square side = 1.0 @ (1.0, 2.0)
- ....
-
- ==== Tracing - Version 1
-
- In a first attempt to insert tracing in this application, we will start
- by writing a `Trace` class that is exactly what we would write if we
- didn't have aspects. The implementation is in `version1/Trace.java`. Its
- public interface is:
-
- [source, java]
- ....
- public class Trace {
- public static int TRACELEVEL = 0;
- public static void initStream(PrintStream s) {...}
- public static void traceEntry(String str) {...}
- public static void traceExit(String str) {...}
- }
- ....
-
- If we didn't have AspectJ, we would have to insert calls to `traceEntry`
- and `traceExit` in all methods and constructors we wanted to trace, and
- to initialize `TRACELEVEL` and the stream. If we wanted to trace all the
- methods and constructors in our example, that would amount to around 40
- calls, and we would hope we had not forgotten any method. But we can do
- that more consistently and reliably with the following aspect (found in
- `version1/TraceMyClasses.java`):
-
- [source, java]
- ....
- public aspect TraceMyClasses {
- pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);
- pointcut myConstructor(): myClass() && execution(new(..));
- pointcut myMethod(): myClass() && execution(* *(..));
-
- before (): myConstructor() {
- Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());
- }
- after(): myConstructor() {
- Trace.traceExit("" + thisJoinPointStaticPart.getSignature());
- }
-
- before (): myMethod() {
- Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());
- }
- after(): myMethod() {
- Trace.traceExit("" + thisJoinPointStaticPart.getSignature());
- }
- }
- ....
-
- This aspect performs the tracing calls at appropriate times. According
- to this aspect, tracing is performed at the entrance and exit of every
- method and constructor defined within the shape hierarchy.
-
- What is printed at before and after each of the traced join points is
- the signature of the method executing. Since the signature is static
- information, we can get it through `thisJoinPointStaticPart`.
-
- To run this version of tracing, go to the directory
- `InstallDir/examples` and type:
-
- [source, text]
- ....
- ajc -argfile tracing/tracev1.lst
- ....
-
- Running the main method of `tracing.version1.TraceMyClasses` should
- produce the output:
-
- [source, text]
- ....
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Circle(double, double, double)
- <-- tracing.Circle(double, double, double)
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Circle(double, double, double)
- <-- tracing.Circle(double, double, double)
- --> tracing.Circle(double)
- <-- tracing.Circle(double)
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Square(double, double, double)
- <-- tracing.Square(double, double, double)
- --> tracing.Square(double, double)
- <-- tracing.Square(double, double)
- --> double tracing.Circle.perimeter()
- <-- double tracing.Circle.perimeter()
- c1.perimeter() = 12.566370614359172
- --> double tracing.Circle.area()
- <-- double tracing.Circle.area()
- c1.area() = 12.566370614359172
- --> double tracing.Square.perimeter()
- <-- double tracing.Square.perimeter()
- s1.perimeter() = 4.0
- --> double tracing.Square.area()
- <-- double tracing.Square.area()
- s1.area() = 1.0
- --> double tracing.TwoDShape.distance(TwoDShape)
- --> double tracing.TwoDShape.getX()
- <-- double tracing.TwoDShape.getX()
- --> double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.distance(TwoDShape)
- c2.distance(c1) = 4.242640687119285
- --> double tracing.TwoDShape.distance(TwoDShape)
- --> double tracing.TwoDShape.getX()
- <-- double tracing.TwoDShape.getX()
- --> double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.distance(TwoDShape)
- s1.distance(c1) = 2.23606797749979
- --> String tracing.Square.toString()
- --> String tracing.TwoDShape.toString()
- <-- String tracing.TwoDShape.toString()
- <-- String tracing.Square.toString()
- s1.toString(): Square side = 1.0 @ (1.0, 2.0)
- ....
-
- When `TraceMyClasses.java` is not provided to `ajc`, the aspect does not
- have any affect on the system and the tracing is unplugged.
-
- ==== Tracing - Version 2
-
- Another way to accomplish the same thing would be to write a reusable
- tracing aspect that can be used not only for these application classes,
- but for any class. One way to do this is to merge the tracing
- functionality of `Trace - version1` with the crosscutting support of
- `TraceMyClasses - version1`. We end up with a `Trace` aspect (found in
- `version2/Trace.java`) with the following public interface
-
- [source, java]
- ....
- abstract aspect Trace {
- public static int TRACELEVEL = 2;
- public static void initStream(PrintStream s) {...}
- protected static void traceEntry(String str) {...}
- protected static void traceExit(String str) {...}
- abstract pointcut myClass();
- }
- ....
-
- In order to use it, we need to define our own subclass that knows about
- our application classes, in `version2/TraceMyClasses.java`:
-
- [source, java]
- ....
- public aspect TraceMyClasses extends Trace {
- pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);
-
- public static void main(String[] args) {
- Trace.TRACELEVEL = 2;
- Trace.initStream(System.err);
- ExampleMain.main(args);
- }
- }
- ....
-
- Notice that we've simply made the pointcut `classes`, that was an
- abstract pointcut in the super-aspect, concrete. To run this version of
- tracing, go to the directory `examples` and type:
-
- [source, text]
- ....
- ajc -argfile tracing/tracev2.lst
- ....
-
- The file `tracev2.lst` lists the application classes as well as this
- version of the files Trace.java and TraceMyClasses.java. Running the
- main method of `tracing.version2.TraceMyClasses` should output exactly
- the same trace information as that from version 1.
-
- The entire implementation of the new `Trace` class is:
-
- [source, java]
- ....
- abstract aspect Trace {
-
- // implementation part
-
- public static int TRACELEVEL = 2;
- protected static PrintStream stream = System.err;
- protected static int callDepth = 0;
-
- public static void initStream(PrintStream s) {
- stream = s;
- }
- protected static void traceEntry(String str) {
- if (TRACELEVEL == 0) return;
- if (TRACELEVEL == 2) callDepth++;
- printEntering(str);
- }
- protected static void traceExit(String str) {
- if (TRACELEVEL == 0) return;
- printExiting(str);
- if (TRACELEVEL == 2) callDepth--;
- }
- private static void printEntering(String str) {
- printIndent();
- stream.println("--> " + str);
- }
- private static void printExiting(String str) {
- printIndent();
- stream.println("<-- " + str);
- }
- private static void printIndent() {
- for (int i = 0; i < callDepth; i++)
- stream.print(" ");
- }
-
- // protocol part
-
- abstract pointcut myClass();
-
- pointcut myConstructor(): myClass() && execution(new(..));
- pointcut myMethod(): myClass() && execution(* *(..));
-
- before(): myConstructor() {
- traceEntry("" + thisJoinPointStaticPart.getSignature());
- }
- after(): myConstructor() {
- traceExit("" + thisJoinPointStaticPart.getSignature());
- }
-
- before(): myMethod() {
- traceEntry("" + thisJoinPointStaticPart.getSignature());
- }
- after(): myMethod() {
- traceExit("" + thisJoinPointStaticPart.getSignature());
- }
- }
- ....
-
- This version differs from version 1 in several subtle ways. The first
- thing to notice is that this `Trace` class merges the functional part of
- tracing with the crosscutting of the tracing calls. That is, in version
- 1, there was a sharp separation between the tracing support (the class
- `Trace`) and the crosscutting usage of it (by the class
- `TraceMyClasses`). In this version those two things are merged. That's
- why the description of this class explicitly says that "Trace messages
- are printed before and after constructors and methods are," which is
- what we wanted in the first place. That is, the placement of the calls,
- in this version, is established by the aspect class itself, leaving less
- opportunity for misplacing calls.
-
- A consequence of this is that there is no need for providing
- `traceEntry` and `traceExit` as public operations of this class. You can
- see that they were classified as protected. They are supposed to be
- internal implementation details of the advice.
-
- The key piece of this aspect is the abstract pointcut classes that
- serves as the base for the definition of the pointcuts constructors and
- methods. Even though `classes` is abstract, and therefore no concrete
- classes are mentioned, we can put advice on it, as well as on the
- pointcuts that are based on it. The idea is "we don't know exactly what
- the pointcut will be, but when we do, here's what we want to do with
- it." In some ways, abstract pointcuts are similar to abstract methods.
- Abstract methods don't provide the implementation, but you know that the
- concrete subclasses will, so you can invoke those methods.
-
- [[examples-production]]
- == Production Aspects
-
- === A Bean Aspect
-
- (The code for this example is in `InstallDir/examples/bean`.)
-
- This example examines an aspect that makes Point objects into Java beans
- with bound properties.
-
- Java beans are reusable software components that can be visually
- manipulated in a builder tool. The requirements for an object to be a
- bean are few. Beans must define a no-argument constructor and must be
- either `Serializable` or `Externalizable`. Any properties of the object
- that are to be treated as bean properties should be indicated by the
- presence of appropriate `get` and `set` methods whose names are
- `get__property__` and `set__property__` where `__property__` is the name of
- a field in the bean class. Some bean properties, known as bound
- properties, fire events whenever their values change so that any
- registered listeners (such as, other beans) will be informed of those
- changes. Making a bound property involves keeping a list of registered
- listeners, and creating and dispatching event objects in methods that
- change the property values, such as `set__property__` methods.
-
- `Point` is a simple class representing points with rectangular
- coordinates. `Point` does not know anything about being a bean: there
- are set methods for `x` and `y` but they do not fire events, and the
- class is not serializable. Bound is an aspect that makes `Point` a
- serializable class and makes its `get` and `set` methods support the
- bound property protocol.
-
- ==== The `Point` class
-
- The `Point` class is a very simple class with trivial getters and
- setters, and a simple vector offset method.
-
- [source, java]
- ....
- class Point {
-
- protected int x = 0;
- protected int y = 0;
-
- public int getX() {
- return x;
- }
-
- public int getY() {
- return y;
- }
-
- public void setRectangular(int newX, int newY) {
- setX(newX);
- setY(newY);
- }
-
- public void setX(int newX) {
- x = newX;
- }
-
- public void setY(int newY) {
- y = newY;
- }
-
- public void offset(int deltaX, int deltaY) {
- setRectangular(x + deltaX, y + deltaY);
- }
-
- public String toString() {
- return "(" + getX() + ", " + getY() + ")" ;
- }
- }
- ....
-
- ==== The `BoundPoint` aspect
-
- The `BoundPoint` aspect is responsible for ``Point``'s "beanness". The
- first thing it does is privately declare that each `Point` has a
- `support` field that holds reference to an instance of
- `PropertyChangeSupport`.
-
- [source, java]
- ....
- private PropertyChangeSupport Point.support = new PropertyChangeSupport(this);
- ....
-
- The property change support object must be constructed with a reference
- to the bean for which it is providing support, so it is initialized by
- passing it `this`, an instance of `Point`. Since the `support` field is
- private declared in the aspect, only the code in the aspect can refer to
- it.
-
- The aspect also declares ``Point``'s methods for registering and managing
- listeners for property change events, which delegate the work to the
- property change support object:
-
- [source, java]
- ....
- public void Point.addPropertyChangeListener(PropertyChangeListener listener){
- support.addPropertyChangeListener(listener);
- }
- public void Point.addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
- support.addPropertyChangeListener(propertyName, listener);
- }
- public void Point.removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
- support.removePropertyChangeListener(propertyName, listener);
- }
- public void Point.removePropertyChangeListener(PropertyChangeListener listener) {
- support.removePropertyChangeListener(listener);
- }
- public void Point.hasListeners(String propertyName) {
- support.hasListeners(propertyName);
- }
- ....
-
- The aspect is also responsible for making sure `Point` implements the
- `Serializable` interface:
-
- [source, java]
- ....
- declare parents: Point implements Serializable;
- ....
-
- Implementing this interface in Java does not require any methods to be
- implemented. Serialization for `Point` objects is provided by the
- default serialization method.
-
- The `setters` pointcut picks out calls to the ``Point``'s `set` methods:
- any method whose name begins with "`set`" and takes one parameter. The
- around advice on `setters()` stores the values of the `X` and `Y`
- properties, calls the original `set` method and then fires the
- appropriate property change event according to which set method was
- called.
-
- [source, java]
- ....
- aspect BoundPoint {
- private PropertyChangeSupport Point.support = new PropertyChangeSupport(this);
-
- public void Point.addPropertyChangeListener(PropertyChangeListener listener) {
- support.addPropertyChangeListener(listener);
- }
- public void Point.addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
- support.addPropertyChangeListener(propertyName, listener);
- }
- public void Point.removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
- support.removePropertyChangeListener(propertyName, listener);
- }
- public void Point.removePropertyChangeListener(PropertyChangeListener listener) {
- support.removePropertyChangeListener(listener);
- }
- public void Point.hasListeners(String propertyName) {
- support.hasListeners(propertyName);
- }
-
- declare parents: Point implements Serializable;
-
- pointcut setter(Point p): call(void Point.set*(*)) && target(p);
-
- void around(Point p): setter(p) {
- String propertyName =
- thisJoinPointStaticPart.getSignature().getName().substring("set".length());
- int oldX = p.getX();
- int oldY = p.getY();
- proceed(p);
- if (propertyName.equals("X")){
- firePropertyChange(p, propertyName, oldX, p.getX());
- } else {
- firePropertyChange(p, propertyName, oldY, p.getY());
- }
- }
-
- void firePropertyChange(Point p, String property, double oldval, double newval) {
- p.support.firePropertyChange(property, new Double(oldval), new Double(newval));
- }
- }
- ....
-
- ==== The Test Program
-
- The test program registers itself as a property change listener to a
- `Point` object that it creates and then performs simple manipulation of
- that point: calling its set methods and the offset method. Then it
- serializes the point and writes it to a file and then reads it back. The
- result of saving and restoring the point is that a new point is created.
-
- [source, java]
- ....
- class Demo implements PropertyChangeListener {
-
- static final String fileName = "test.tmp";
-
- public void propertyChange(PropertyChangeEvent e){
- System.out.println(
- "Property " + e.getPropertyName() + " changed from " +
- e.getOldValue() + " to " + e.getNewValue()
- );
- }
-
- public static void main(String[] args) {
- Point p1 = new Point();
- p1.addPropertyChangeListener(new Demo());
- System.out.println("p1 =" + p1);
- p1.setRectangular(5,2);
- System.out.println("p1 =" + p1);
- p1.setX( 6 );
- p1.setY( 3 );
- System.out.println("p1 =" + p1);
- p1.offset(6,4);
- System.out.println("p1 =" + p1);
- save(p1, fileName);
- Point p2 = (Point) restore(fileName);
- System.out.println("Had: " + p1);
- System.out.println("Got: " + p2);
- }
- // ...
- }
- ....
-
- ==== Compiling and Running the Example
-
- To compile and run this example, go to the examples directory and type:
-
- [source, text]
- ....
- ajc -argfile bean/files.lst
- java bean.Demo
- ....
-
- [[the-subject-observer-protocol]]
- === The Subject/Observer Protocol
-
- (The code for this example is in `InstallDir/examples/observer`.)
-
- This demo illustrates how the Subject/Observer design pattern can be
- coded with aspects.
-
- The demo consists of the following: A colored label is a renderable
- object that has a color that cycles through a set of colors, and a
- number that records the number of cycles it has been through. A button
- is an action item that records when it is clicked.
-
- With these two kinds of objects, we can build up a Subject/Observer
- relationship in which colored labels observe the clicks of buttons; that
- is, where colored labels are the observers and buttons are the subjects.
-
- The demo is designed and implemented using the Subject/Observer design
- pattern. The remainder of this example explains the classes and aspects
- of this demo, and tells you how to run it.
-
- ==== Generic Components
-
- The generic parts of the protocol are the interfaces `Subject` and
- `Observer`, and the abstract aspect `SubjectObserverProtocol`. The
- `Subject` interface is simple, containing methods to add, remove, and
- view `Observer` objects, and a method for getting data about state
- changes:
-
- [source, java]
- ....
- interface Subject {
- void addObserver(Observer obs);
- void removeObserver(Observer obs);
- Vector getObservers();
- Object getData();
- }
- ....
-
- The `Observer` interface is just as simple, with methods to set and get
- `Subject` objects, and a method to call when the subject gets updated.
-
- [source, java]
- ....
- interface Observer {
- void setSubject(Subject s);
- Subject getSubject();
- void update();
- }
- ....
-
- The `SubjectObserverProtocol` aspect contains within it all of the
- generic parts of the protocol, namely, how to fire the `Observer`
- objects' update methods when some state changes in a subject.
-
- [source, java]
- ....
- abstract aspect SubjectObserverProtocol {
-
- abstract pointcut stateChanges(Subject s);
-
- after(Subject s): stateChanges(s) {
- for (int i = 0; i < s.getObservers().size(); i++) {
- ((Observer)s.getObservers().elementAt(i)).update();
- }
- }
-
- private Vector Subject.observers = new Vector();
- public void Subject.addObserver(Observer obs) {
- observers.addElement(obs);
- obs.setSubject(this);
- }
- public void Subject.removeObserver(Observer obs) {
- observers.removeElement(obs);
- obs.setSubject(null);
- }
- public Vector Subject.getObservers() { return observers; }
-
- private Subject Observer.subject = null;
- public void Observer.setSubject(Subject s) { subject = s; }
- public Subject Observer.getSubject() { return subject; }
-
- }
- ....
-
- Note that this aspect does three things. It define an abstract pointcut
- that extending aspects can override. It defines advice that should run
- after the join points of the pointcut. And it declares an inter-type
- field and two inter-type methods so that each `Observer` can hold onto
- its `Subject`.
-
- ==== Application Classes
-
- `Button` objects extend `java.awt.Button`, and all they do is make sure
- the `void click()` method is called whenever a button is clicked.
-
- [source, java]
- ....
- class Button extends java.awt.Button {
-
- static final Color defaultBackgroundColor = Color.gray;
- static final Color defaultForegroundColor = Color.black;
- static final String defaultText = "cycle color";
-
- Button(Display display) {
- super();
- setLabel(defaultText);
- setBackground(defaultBackgroundColor);
- setForeground(defaultForegroundColor);
- addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Button.this.click();
- }
- });
- display.addToFrame(this);
- }
-
- public void click() {}
- }
- ....
-
- Note that this class knows nothing about being a Subject.
-
- ColorLabel objects are labels that support the void colorCycle() method.
- Again, they know nothing about being an observer.
-
- [source, java]
- ....
- class ColorLabel extends Label {
-
- ColorLabel(Display display) {
- super();
- display.addToFrame(this);
- }
-
- final static Color[] colors =
- { Color.red, Color.blue, Color.green, Color.magenta };
- private int colorIndex = 0;
- private int cycleCount = 0;
- void colorCycle() {
- cycleCount++;
- colorIndex = (colorIndex + 1) % colors.length;
- setBackground(colors[colorIndex]);
- setText("" + cycleCount);
- }
- }
- ....
-
- Finally, the `SubjectObserverProtocolImpl` implements the
- subject/observer protocol, with `Button` objects as subjects and
- `ColorLabel` objects as observers:
-
- [source, java]
- ....
- package observer;
-
- import java.util.Vector;
-
- aspect SubjectObserverProtocolImpl extends SubjectObserverProtocol {
-
- declare parents: Button implements Subject;
- public Object Button.getData() { return this; }
-
- declare parents: ColorLabel implements Observer;
- public void ColorLabel.update() {
- colorCycle();
- }
-
- pointcut stateChanges(Subject s):
- target(s) &&
- call(void Button.click());
-
- }
- ....
-
- It does this by assuring that `Button` and `ColorLabel` implement the
- appropriate interfaces, declaring that they implement the methods
- required by those interfaces, and providing a definition for the
- abstract `stateChanges` pointcut. Now, every time a `Button` is clicked,
- all `ColorLabel` objects observing that button will `colorCycle`.
-
- ==== Compiling and Running
-
- `Demo` is the top class that starts this demo. It instantiates a two
- buttons and three observers and links them together as subjects and
- observers. So to run the demo, go to the `examples` directory and type:
-
- [source, text]
- ....
- ajc -argfile observer/files.lst
- java observer.Demo
- ....
-
- === A Simple Telecom Simulation
-
- (The code for this example is in `InstallDir/examples/telecom`.)
-
- This example illustrates some ways that dependent concerns can be
- encoded with aspects. It uses an example system comprising a simple
- model of telephone connections to which timing and billing features are
- added using aspects, where the billing feature depends upon the timing
- feature.
-
- ==== The Application
-
- The example application is a simple simulation of a telephony system in
- which customers make, accept, merge and hang-up both local and long
- distance calls. The application architecture is in three layers.
-
- * The basic objects provide basic functionality to simulate customers,
- calls and connections (regular calls have one connection, conference
- calls have more than one).
- * The timing feature is concerned with timing the connections and
- keeping the total connection time per customer. Aspects are used to add
- a timer to each connection and to manage the total time per customer.
- * The billing feature is concerned with charging customers for the calls
- they make. Aspects are used to calculate a charge per connection and,
- upon termination of a connection, to add the charge to the appropriate
- customer's bill. The billing aspect builds upon the timing aspect: it
- uses a pointcut defined in Timing and it uses the timers that are
- associated with connections.
-
- The simulation of system has three configurations: basic, timing and
- billing. Programs for the three configurations are in classes
- `BasicSimulation`, `TimingSimulation` and `BillingSimulation`. These
- share a common superclass `AbstractSimulation`, which defines the method
- run with the simulation itself and the method wait used to simulate
- elapsed time.
-
- ==== The Basic Objects
-
- The telecom simulation comprises the classes `Customer`, `Call` and the
- abstract class `Connection` with its two concrete subclasses `Local` and
- `LongDistance`. Customers have a name and a numeric area code. They also
- have methods for managing calls. Simple calls are made between one
- customer (the caller) and another (the receiver), a `Connection` object
- is used to connect them. Conference calls between more than two
- customers will involve more than one connection. A customer may be
- involved in many calls at one time.
-
- image:images/telecom.png[image]
-
- ==== The `Customer` class
-
- `Customer` has methods `call`, `pickup`, `hangup` and `merge` for
- managing calls.
-
- [source, java]
- ....
- public class Customer {
-
- private String name;
- private int areacode;
- private Vector calls = new Vector();
-
- protected void removeCall(Call c){
- calls.removeElement(c);
- }
-
- protected void addCall(Call c){
- calls.addElement(c);
- }
-
- public Customer(String name, int areacode) {
- this.name = name;
- this.areacode = areacode;
- }
-
- public String toString() {
- return name + "(" + areacode + ")";
- }
-
- public int getAreacode(){
- return areacode;
- }
-
- public boolean localTo(Customer other){
- return areacode == other.areacode;
- }
-
- public Call call(Customer receiver) {
- Call call = new Call(this, receiver);
- addCall(call);
- return call;
- }
-
- public void pickup(Call call) {
- call.pickup();
- addCall(call);
- }
-
- public void hangup(Call call) {
- call.hangup(this);
- removeCall(call);
- }
-
- public void merge(Call call1, Call call2){
- call1.merge(call2);
- removeCall(call2);
- }
- }
- ....
-
- ==== The `Call` class
-
- Calls are created with a caller and receiver who are customers. If the
- caller and receiver have the same area code then the call can be
- established with a `Local` connection (see below), otherwise a
- `LongDistance` connection is required. A call comprises a number of
- connections between customers. Initially there is only the connection
- between the caller and receiver but additional connections can be added
- if calls are merged to form conference calls.
-
- ==== The `Connection` class
-
- The class `Connection` models the physical details of establishing a
- connection between customers. It does this with a simple state machine
- (connections are initially `PENDING`, then `COMPLETED` and finally
- `DROPPED`). Messages are printed to the console so that the state of
- connections can be observed. Connection is an abstract class with two
- concrete subclasses: `Local` and `LongDistance`.
-
- [source, java]
- ....
- abstract class Connection {
-
- public static final int PENDING = 0;
- public static final int COMPLETE = 1;
- public static final int DROPPED = 2;
-
- Customer caller, receiver;
- private int state = PENDING;
-
- Connection(Customer a, Customer b) {
- this.caller = a;
- this.receiver = b;
- }
-
- public int getState(){
- return state;
- }
-
- public Customer getCaller() { return caller; }
-
- public Customer getReceiver() { return receiver; }
-
- void complete() {
- state = COMPLETE;
- System.out.println("connection completed");
- }
-
- void drop() {
- state = DROPPED;
- System.out.println("connection dropped");
- }
-
- public boolean connects(Customer c){
- return (caller == c || receiver == c);
- }
-
- }
- ....
-
- ==== The `Local` and `LongDistance` classes
-
- The two kinds of connections supported by our simulation are `Local` and
- `LongDistance` connections.
-
- [source, java]
- ....
- class Local extends Connection {
- Local(Customer a, Customer b) {
- super(a, b);
- System.out.println(
- "[new local connection from " + a + " to " + b + "]"
- );
- }
- }
- ....
-
- [source, java]
- ....
- class LongDistance extends Connection {
- LongDistance(Customer a, Customer b) {
- super(a, b);
- System.out.println(
- "[new long distance connection from " + a + " to " + b + "]"
- );
- }
- }
- ....
-
- ==== Compiling and Running the Basic Simulation
-
- The source files for the basic system are listed in the file
- `basic.lst`. To build and run the basic system, in a shell window, type
- these commands:
-
- [source, text]
- ....
- ajc -argfile telecom/basic.lst
- java telecom.BasicSimulation
- ....
-
- ==== The Timing aspect
-
- The `Timing` aspect keeps track of total connection time for each
- `Customer` by starting and stopping a timer associated with each
- connection. It uses some helper classes:
-
- ===== The `Timer` class
-
- A `Timer` object simply records the current time when it is started and
- stopped, and returns their difference when asked for the elapsed time.
- The aspect `TimerLog` (below) can be used to cause the start and stop
- times to be printed to standard output.
-
- [source, java]
- ....
- class Timer {
- long startTime, stopTime;
-
- public void start() {
- startTime = System.currentTimeMillis();
- stopTime = startTime;
- }
-
- public void stop() {
- stopTime = System.currentTimeMillis();
- }
-
- public long getTime() {
- return stopTime - startTime;
- }
- }
- ....
-
- ==== The `TimerLog` aspect
-
- The `TimerLog` aspect can be included in a build to get the timer to
- announce when it is started and stopped.
-
- [source, java]
- ....
- public aspect TimerLog {
-
- after(Timer t): target(t) && call(* Timer.start()) {
- System.err.println("Timer started: " + t.startTime);
- }
-
- after(Timer t): target(t) && call(* Timer.stop()) {
- System.err.println("Timer stopped: " + t.stopTime);
- }
- }
- ....
-
- ==== The `Timing` aspect
-
- The `Timing` aspect is declares an inter-type field `totalConnectTime`
- for `Customer` to store the accumulated connection time per `Customer`.
- It also declares that each `Connection` object has a timer.
-
- [source, java]
- ....
- public long Customer.totalConnectTime = 0;
- private Timer Connection.timer = new Timer();
- ....
-
- Two pieces of after advice ensure that the timer is started when a
- connection is completed and and stopped when it is dropped. The pointcut
- `endTiming` is defined so that it can be used by the `Billing` aspect.
-
- [source, java]
- ....
- public aspect Timing {
-
- public long Customer.totalConnectTime = 0;
-
- public long getTotalConnectTime(Customer cust) {
- return cust.totalConnectTime;
- }
- private Timer Connection.timer = new Timer();
- public Timer getTimer(Connection conn) { return conn.timer; }
-
- after (Connection c): target(c) && call(void Connection.complete()) {
- getTimer(c).start();
- }
-
- pointcut endTiming(Connection c): target(c) &&
- call(void Connection.drop());
-
- after(Connection c): endTiming(c) {
- getTimer(c).stop();
- c.getCaller().totalConnectTime += getTimer(c).getTime();
- c.getReceiver().totalConnectTime += getTimer(c).getTime();
- }
- }
- ....
-
- ==== The `Billing` aspect
-
- The Billing system adds billing functionality to the telecom application
- on top of timing.
-
- The `Billing` aspect declares that each `Connection` has a `payer`
- inter-type field to indicate who initiated the call and therefore who is
- responsible to pay for it. It also declares the inter-type method
- `callRate` of `Connection` so that local and long distance calls can be
- charged differently. The call charge must be calculated after the timer
- is stopped; the after advice on pointcut `Timing.endTiming` does this,
- and `Billing` is declared to be more precedent than `Timing` to make
- sure that this advice runs after ``Timing``'s advice on the same join
- point. Finally, it declares inter-type methods and fields for `Customer`
- to handle the `totalCharge`.
-
- [source, java]
- ....
- public aspect Billing {
- // precedence required to get advice on endtiming in the right order
- declare precedence: Billing, Timing;
-
- public static final long LOCAL_RATE = 3;
- public static final long LONG_DISTANCE_RATE = 10;
-
- public Customer Connection.payer;
- public Customer getPayer(Connection conn) { return conn.payer; }
-
- after(Customer cust) returning (Connection conn):
- args(cust, ..) && call(Connection+.new(..)) {
- conn.payer = cust;
- }
-
- public abstract long Connection.callRate();
-
- public long LongDistance.callRate() { return LONG_DISTANCE_RATE; }
- public long Local.callRate() { return LOCAL_RATE; }
-
- after(Connection conn): Timing.endTiming(conn) {
- long time = Timing.aspectOf().getTimer(conn).getTime();
- long rate = conn.callRate();
- long cost = rate * time;
- getPayer(conn).addCharge(cost);
- }
-
- public long Customer.totalCharge = 0;
- public long getTotalCharge(Customer cust) { return cust.totalCharge; }
-
- public void Customer.addCharge(long charge) {
- totalCharge += charge;
- }
- }
- ....
-
- ==== Accessing the inter-type state
-
- Both the aspects `Timing` and `Billing` contain the definition of
- operations that the rest of the system may want to access. For example,
- when running the simulation with one or both aspects, we want to find
- out how much time each customer spent on the telephone and how big their
- bill is. That information is also stored in the classes, but they are
- accessed through static methods of the aspects, since the state they
- refer to is private to the aspect.
-
- Take a look at the file `TimingSimulation.java`. The most important
- method of this class is the method `report(Customer)`, which is used in
- the method run of the superclass `AbstractSimulation`. This method is
- intended to print out the status of the customer, with respect to the
- `Timing` feature.
-
- [source, java]
- ....
- protected void report(Customer c){
- Timing t = Timing.aspectOf();
- System.out.println(c + " spent " + t.getTotalConnectTime(c));
- }
- ....
-
- ==== Compiling and Running
-
- The files timing.lst and billing.lst contain file lists for the timing
- and billing configurations. To build and run the application with only
- the timing feature, go to the directory examples and type:
-
- [source, text]
- ....
- ajc -argfile telecom/timing.lst
- java telecom.TimingSimulation
- ....
-
- To build and run the application with the timing and billing features,
- go to the directory examples and type:
-
- [source, text]
- ....
- ajc -argfile telecom/billing.lst
- java telecom.BillingSimulation
- ....
-
- ==== Discussion
-
- There are some explicit dependencies between the aspects `Billing` and
- `Timing`:
-
- * `Billing` is declared more precedent than `Timing` so that ``Billing``'s after
- advice runs after that of `Timing` when they are on the same join point.
- * `Billing` uses the pointcut `Timing.endTiming`.
- * `Billing` needs access to the timer associated with a connection.
-
- [[examples-reusable]]
- == Reusable Aspects
-
- === Tracing using Aspects, Revisited
-
- (The code for this example is in `InstallDir/examples/tracing`.)
-
- ==== Tracing - Version 3
-
- One advantage of not exposing the methods `traceEntry` and `traceExit` as
- public operations is that we can easily change their interface without
- any dramatic consequences in the rest of the code.
-
- Consider, again, the program without AspectJ. Suppose, for example, that
- at some point later the requirements for tracing change, stating that
- the trace messages should always include the string representation of
- the object whose methods are being traced. This can be achieved in at
- least two ways. One way is keep the interface of the methods
- `traceEntry` and `traceExit` as it was before,
-
- [source, java]
- ....
- public static void traceEntry(String str);
- public static void traceExit(String str);
- ....
-
- In this case, the caller is responsible for ensuring that the string
- representation of the object is part of the string given as argument.
- So, calls must look like:
-
- [source, java]
- ....
- Trace.traceEntry("Square.distance in " + toString());
- ....
-
- Another way is to enforce the requirement with a second argument in the
- trace operations, e.g.
-
- [source, java]
- ....
- public static void traceEntry(String str, Object obj);
- public static void traceExit(String str, Object obj);
- ....
-
- In this case, the caller is still responsible for sending the right
- object, but at least there is some guarantees that some object will be
- passed. The calls will look like:
-
- [source, java]
- ....
- Trace.traceEntry("Square.distance", this);
- ....
-
- In either case, this change to the requirements of tracing will have
- dramatic consequences in the rest of the code -- every call to the trace
- operations `traceEntry` and `traceExit` must be changed!
-
- Here's another advantage of doing tracing with an aspect. We've already
- seen that in version 2 `traceEntry` and `traceExit` are not publicly
- exposed. So changing their interfaces, or the way they are used, has
- only a small effect inside the `Trace` class. Here's a partial view at
- the implementation of `Trace`, version 3. The differences with respect
- to version 2 are stressed in the comments:
-
- [source, java]
- ....
- abstract aspect Trace {
-
- public static int TRACELEVEL = 0;
- protected static PrintStream stream = null;
- protected static int callDepth = 0;
-
- public static void initStream(PrintStream s) {
- stream = s;
- }
-
- protected static void traceEntry(String str, Object o) {
- if (TRACELEVEL == 0) return;
- if (TRACELEVEL == 2) callDepth++;
- printEntering(str + ": " + o.toString());
- }
-
- protected static void traceExit(String str, Object o) {
- if (TRACELEVEL == 0) return;
- printExiting(str + ": " + o.toString());
- if (TRACELEVEL == 2) callDepth--;
- }
-
- private static void printEntering(String str) {
- printIndent();
- stream.println("Entering " + str);
- }
-
- private static void printExiting(String str) {
- printIndent();
- stream.println("Exiting " + str);
- }
-
- private static void printIndent() {
- for (int i = 0; i < callDepth; i++)
- stream.print(" ");
- }
-
- abstract pointcut myClass(Object obj);
-
- pointcut myConstructor(Object obj): myClass(obj) && execution(new(..));
- pointcut myMethod(Object obj):
- myClass(obj) && execution(* *(..)) && !execution(String toString());
-
- before(Object obj): myConstructor(obj) {
- traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);
- }
- after(Object obj): myConstructor(obj) {
- traceExit("" + thisJoinPointStaticPart.getSignature(), obj);
- }
-
- before(Object obj): myMethod(obj) {
- traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);
- }
- after(Object obj): myMethod(obj) {
- traceExit("" + thisJoinPointStaticPart.getSignature(), obj);
- }
- }
- ....
-
- As you can see, we decided to apply the first design by preserving the
- interface of the methods `traceEntry` and `traceExit`. But it doesn't
- matter - we could as easily have applied the second design (the code in
- the directory `examples/tracing/version3` has the second design). The
- point is that the effects of this change in the tracing requirements are
- limited to the `Trace` aspect class.
-
- One implementation change worth noticing is the specification of the
- pointcuts. They now expose the object. To maintain full consistency with
- the behavior of version 2, we should have included tracing for static
- methods, by defining another pointcut for static methods and advising
- it. We leave that as an exercise.
-
- Moreover, we had to exclude the execution join point of the method
- `toString` from the `methods` pointcut. The problem here is that
- `toString` is being called from inside the advice. Therefore if we trace
- it, we will end up in an infinite recursion of calls. This is a subtle
- point, and one that you must be aware when writing advice. If the advice
- calls back to the objects, there is always the possibility of recursion.
- Keep that in mind!
-
- In fact, esimply excluding the execution join point may not be enough,
- if there are calls to other traced methods within it - in which case,
- the restriction should be
-
- [source, java]
- ....
- && !cflow(execution(String toString()))
- ....
-
- excluding both the execution of `toString` methods and all join points
- under that execution.
-
- In summary, to implement the change in the tracing requirements we had
- to make a couple of changes in the implementation of the `Trace` aspect
- class, including changing the specification of the pointcuts. That's
- only natural. But the implementation changes were limited to this
- aspect. Without aspects, we would have to change the implementation of
- every application class.
-
- Finally, to run this version of tracing, go to the directory `examples`
- and type:
-
- [source, text]
- ....
- ajc -argfile tracing/tracev3.lst
- ....
-
- The file `tracev3.lst` lists the application classes as well as this
- version of the files `Trace.java` and `TraceMyClasses.java`. To run the
- program, type
-
- [source, text]
- ....
- java tracing.version3.TraceMyClasses
- ....
-
- The output should be:
-
- [source, text]
- ....
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Circle(double, double, double)
- <-- tracing.Circle(double, double, double)
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Circle(double, double, double)
- <-- tracing.Circle(double, double, double)
- --> tracing.Circle(double)
- <-- tracing.Circle(double)
- --> tracing.TwoDShape(double, double)
- <-- tracing.TwoDShape(double, double)
- --> tracing.Square(double, double, double)
- <-- tracing.Square(double, double, double)
- --> tracing.Square(double, double)
- <-- tracing.Square(double, double)
- --> double tracing.Circle.perimeter()
- <-- double tracing.Circle.perimeter()
- c1.perimeter() = 12.566370614359172
- --> double tracing.Circle.area()
- <-- double tracing.Circle.area()
- c1.area() = 12.566370614359172
- --> double tracing.Square.perimeter()
- <-- double tracing.Square.perimeter()
- s1.perimeter() = 4.0
- --> double tracing.Square.area()
- <-- double tracing.Square.area()
- s1.area() = 1.0
- --> double tracing.TwoDShape.distance(TwoDShape)
- --> double tracing.TwoDShape.getX()
- <-- double tracing.TwoDShape.getX()
- --> double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.distance(TwoDShape)
- c2.distance(c1) = 4.242640687119285
- --> double tracing.TwoDShape.distance(TwoDShape)
- --> double tracing.TwoDShape.getX()
- <-- double tracing.TwoDShape.getX()
- --> double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.getY()
- <-- double tracing.TwoDShape.distance(TwoDShape)
- s1.distance(c1) = 2.23606797749979
- --> String tracing.Square.toString()
- --> String tracing.TwoDShape.toString()
- <-- String tracing.TwoDShape.toString()
- <-- String tracing.Square.toString()
- s1.toString(): Square side = 1.0 @ (1.0, 2.0)
- ....
|