12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444 |
- <chapter id="examples" xreflabel="Examples">
- <title>Examples</title>
-
- <sect1 id="examples-intro">
- <title>Introduction</title>
-
- <para>
- This chapter consists entirely of examples of AspectJ use.
- </para>
-
- <para>The examples can be grouped into four categories:</para>
-
- <simplelist columns="2" type="horiz">
- <member><emphasis role="bold">technique</emphasis></member>
- <member>Examples which illustrate how to use one or more features of the
- language. </member>
-
- <member><emphasis role="bold">development</emphasis></member>
- <member>Examples of using AspectJ during the development phase of a
- project. </member>
-
- <member><emphasis role="bold">production</emphasis></member>
- <member>Examples of using AspectJ to provide functionality in an
- application. </member>
-
- <member><emphasis role="bold">reusable</emphasis></member>
- <member>Examples of reuse of aspects and pointcuts.</member>
- </simplelist>
-
- </sect1>
-
- <!-- ============================== -->
-
- <sect1 id="examples-howto">
- <title>Obtaining, Compiling and Running the Examples</title>
-
- <para>
- The examples source code is part of the AspectJ distribution which may be
- downloaded from the AspectJ project page ( <ulink
- url="http://eclipse.org/aspectj" /> ).
- </para>
-
- <para>
- Compiling most examples is straightforward. Go the
- <filename><replaceable>InstallDir</replaceable>/examples</filename>
- directory, and look for a <filename>.lst</filename> file in one of
- the example subdirectories. Use the <literal>-arglist</literal>
- option to <literal>ajc</literal> to compile the example. For
- instance, to compile the telecom example with billing, type
- </para>
-
- <programlisting>
- ajc -argfile telecom/billing.lst
- </programlisting>
-
- <para>
- To run the examples, your classpath must include the AspectJ run-time
- Java archive (<literal>aspectjrt.jar</literal>). You may either set the
- <literal>CLASSPATH</literal> environment variable or use the
- <literal>-classpath</literal> command line option to the Java
- interpreter:
- </para>
-
- <programlisting>
- (In Unix use a : in the CLASSPATH)
- java -classpath ".:<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation
- </programlisting>
-
- <programlisting>
- (In Windows use a ; in the CLASSPATH)
- java -classpath ".;<replaceable>InstallDir</replaceable>/lib/aspectjrt.jar" telecom.billingSimulation
- </programlisting>
-
- </sect1>
-
-
- <!-- ============================================================ -->
-
- <sect1 id="examples-basic">
- <title>Basic Techniques</title>
-
- <para>
- 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.
- </para>
-
- <para>
- The first example, <xref linkend="examples-joinPoints" />, is about
- gathering and using information about the join point that has
- triggered some advice. The second example, <xref
- linkend="examples-roles" />, concerns a crosscutting view of an
- existing class hierarchy. </para>
-
- <!-- ======================================== -->
-
- <sect2 id="examples-joinPoints">
- <title>Join Points and <literal>thisJoinPoint</literal></title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/tjp</filename>.)
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <programlisting><![CDATA[
- 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;
- }
- }
- ]]></programlisting>
-
- <para>
- 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
- </para>
-
- <programlisting><![CDATA[
- pointcut execsInProblemClass(): within(ProblemClass)
- && execution(* *(..));
- ]]></programlisting>
-
- <para>
- will pick out each execution join point of every method defined
- within <classname>ProblemClass</classname>. Since advice executes
- at each join point picked out by the pointcut, we can reasonably
- ask which join point was reached.
- </para>
-
- <para>
- Information about the join point that was matched is available to
- advice through the special variable
- <varname>thisJoinPoint</varname>, of type <ulink
- url="../api/org/aspectj/lang/JoinPoint.html"><classname>org.aspectj.lang.JoinPoint</classname></ulink>.
- Through this object we can access information such as</para>
-
- <itemizedlist spacing="compact">
- <listitem>
- the kind of join point that was matched
- </listitem>
- <listitem>
- the source location of the code associated with the join point
- </listitem>
- <listitem>
- normal, short and long string representations of the
- current join point
- </listitem>
- <listitem>
- the actual argument values of the join point
- </listitem>
- <listitem>
- the signature of the member associated with the join point
- </listitem>
- <listitem>the currently executing object</listitem>
- <listitem>the target object</listitem>
- <listitem>
- an object encapsulating the static information about the join
- point. This is also available through the special variable
- <varname>thisJoinPointStaticPart</varname>.</listitem>
- </itemizedlist>
-
- <sect3>
- <title>The <classname>Demo</classname> class</title>
-
- <para>The class <classname>tjp.Demo</classname> in
- <filename>tjp/Demo.java</filename> defines two methods
- <literal>foo</literal> and <literal>bar</literal> with different
- parameter lists and return types. Both are called, with suitable
- arguments, by <classname>Demo</classname>'s
- <function>go</function> method which was invoked from within its
- <function>main</function> method.
- </para>
-
- <programlisting><![CDATA[
- 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 + ")";
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <literal>GetInfo</literal> aspect</title>
-
- <para>
- This aspect uses around advice to intercept the execution of
- methods <literal>foo</literal> and <literal>bar</literal> in
- <classname>Demo</classname>, and prints out information garnered
- from <literal>thisJoinPoint</literal> to the console.
- </para>
-
- <programlisting><![CDATA[
- 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]);
- }
- }
- }
- ]]></programlisting>
-
- <sect4>
- <title>Defining the scope of a pointcut</title>
-
- <para>The pointcut <function>goCut</function> is defined as
-
- <programlisting><![CDATA[
- cflow(this(Demo)) && execution(void go())
- ]]></programlisting>
-
- so that only executions made in the control flow of
- <literal>Demo.go</literal> are intercepted. The control flow
- from the method <literal>go</literal> includes the execution of
- <literal>go</literal> itself, so the definition of the around
- advice includes <literal>!execution(* go())</literal> to
- exclude it from the set of executions advised. </para>
- </sect4>
-
- <sect4>
- <title>Printing the class and method name</title>
-
- <para>
- The name of the method and that method's defining class are
- available as parts of the <ulink
- url="../api/org/aspectj/lang/Signature.html">org.aspectj.lang.Signature</ulink>
- object returned by calling <literal>getSignature()</literal> on
- either <literal>thisJoinPoint</literal> or
- <literal>thisJoinPointStaticPart</literal>.
- </para>
- </sect4>
-
- <sect4>
- <title>Printing the parameters</title>
-
- <para>
- The static portions of the parameter details, the name and
- types of the parameters, can be accessed through the <ulink
- url="../api/org/aspectj/lang/reflect/CodeSignature.html"><literal>org.aspectj.lang.reflect.CodeSignature</literal></ulink>
- associated with the join point. All execution join points have code
- signatures, so the cast to <literal>CodeSignature</literal>
- cannot fail. </para>
-
- <para>
- The dynamic portions of the parameter details, the actual
- values of the parameters, are accessed directly from the
- execution join point object.
- </para>
- </sect4>
- </sect3>
- </sect2>
-
- <!-- ============================== -->
-
- <sect2 id="examples-roles">
- <title>Roles and Views</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/introduction</filename>.)
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- This is crucial: Publically affecting the class structure of an
- application makes these modifications available to other components
- of the application.
- </para>
-
- <para>
- Aspects can declare inter-type
-
- <itemizedlist spacing="compact">
- <listitem>fields</listitem>
- <listitem>methods</listitem>
- <listitem>constructors</listitem>
- </itemizedlist>
-
- and can also declare that target types
-
- <itemizedlist spacing="compact">
- <listitem>implement new interfaces</listitem>
- <listitem>extend new classes</listitem>
- </itemizedlist>
- </para>
-
- <para>
- 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, <classname>Point</classname>, is a
- simple class with rectangular and polar coordinates. Our inter-type
- declarations will make the class <classname>Point</classname>, in
- turn, cloneable, hashable, and comparable. These facilities are
- provided by AspectJ without having to modify the code for the class
- <classname>Point</classname>.
- </para>
-
- <sect3>
- <title>The <classname>Point</classname> class</title>
-
- <para>The <classname>Point</classname> class defines geometric points
- whose interface includes polar and rectangular coordinates, plus some
- simple operations to relocate points. <classname>Point</classname>'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 <classname>Point</classname> 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.
- </para>
-
- <para>
- The diagram below gives an overview of the aspects and their
- interaction with the class <classname>Point</classname>.</para>
-
- <para>
- <inlinemediaobject>
- <imageobject>
- <imagedata fileref="aspects.gif"/>
- </imageobject>
- </inlinemediaobject>
- </para>
- <para></para>
-
- </sect3>
-
- <sect3>
- <title>The <classname>CloneablePoint</classname> aspect</title>
-
- <para>
- This first aspect is responsible for
- <classname>Point</classname>'s implementation of the
- <classname>Cloneable</classname> interface. It declares that
- <literal>Point implements Cloneable</literal> with a
- <literal>declare parents</literal> form, and also publically
- declares a specialized <literal>Point</literal>'s
- <literal>clone()</literal> method. In Java, all objects inherit
- the method <literal>clone</literal> from the class
- <classname>Object</classname>, but an object is not cloneable
- unless its class also implements the interface
- <classname>Cloneable</classname>. In addition, classes
- frequently have requirements over and above the simple
- bit-for-bit copying that <literal>Object.clone</literal> does. In
- our case, we want to update a <classname>Point</classname>'s
- coordinate systems before we actually clone the
- <classname>Point</classname>. So our aspect makes sure that
- <literal>Point</literal> overrides
- <literal>Object.clone</literal> with a new method that does what
- we want.
- </para>
-
- <para>
- We also define a test <literal>main</literal> method in the
- aspect for convenience.
- </para>
-
- <programlisting><![CDATA[
- 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 );
- }
- }
- ]]></programlisting>
- </sect3>
-
- <sect3>
- <title>The <classname>ComparablePoint</classname> aspect</title>
-
- <para>
- <classname>ComparablePoint</classname> is responsible for
- <literal>Point</literal>'s implementation of the
- <literal>Comparable</literal> interface. </para>
-
- <para>
- The interface <classname>Comparable</classname> defines the
- single method <literal>compareTo</literal> which can be use to define
- a natural ordering relation among the objects of a class that
- implement it.
- </para>
-
- <para>
- <classname>ComparablePoint</classname> uses <literal>declare
- parents</literal> to declare that <literal>Point implements
- Comparable</literal>, and also publically declares the
- appropriate <literal>compareTo(Object)</literal> method: A
- <classname>Point</classname> <literal>p1</literal> is said to be
- less than another <classname>Point</classname><literal>
- p2</literal> if <literal>p1</literal> is closer to the
- origin.
- </para>
-
- <para>
- We also define a test <literal>main</literal> method in the
- aspect for convenience.
- </para>
-
- <programlisting><![CDATA[
- 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));
- }
- }
- ]]></programlisting>
- </sect3>
-
- <sect3>
- <title>The <classname>HashablePoint</classname> aspect</title>
-
- <para>
- Our third aspect is responsible for <literal>Point</literal>'s
- overriding of <literal>Object</literal>'s
- <literal>equals</literal> and <literal>hashCode</literal> methods
- in order to make <literal>Point</literal>s hashable.
- </para>
-
- <para>
- The method <literal>Object.hashCode</literal> 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
- <literal>equals</literal> method) return different integer
- results from <literal>hashCode</literal> 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
- <literal>hashCode</literal>. Since the default implementation
- of <literal>Object.equals</literal> returns <literal>true</literal>
- only when two objects are identical, we need to redefine both
- <function>equals</function> and <function>hashCode</function> to work
- correctly with objects of type <classname>Point</classname>. For
- example, we want two <classname>Point</classname> objects to test
- equal when they have the same <literal>x</literal> and
- <literal>y</literal> values, or the same <literal>rho</literal> and
- <literal>theta</literal> values, not just when they refer to the same
- object. We do this by overriding the methods
- <literal>equals</literal> and <literal>hashCode</literal> in the
- class <classname>Point</classname>.
- </para>
-
- <para>
- So <classname>HashablePoint</classname> declares
- <literal>Point</literal>'s <literal>hashCode</literal> and
- <literal>equals</literal> methods, using
- <classname>Point</classname>'s rectangular coordinates to
- generate a hash code and to test for equality. The
- <literal>x</literal> and <literal>y</literal> coordinates are
- obtained using the appropriate get methods, which ensure the
- rectangular coordinates are up-to-date before returning their
- values.
- </para>
-
- <para>
- And again, we supply a <literal>main</literal> method in the
- aspect for testing.
- </para>
-
- <programlisting><![CDATA[
- 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));
- }
- }
- ]]></programlisting>
-
- </sect3>
- </sect2>
- </sect1>
-
- <!-- ============================================================ -->
- <!-- ============================================================ -->
-
- <sect1 id="examples-development">
- <title>Development Aspects</title>
-
- <sect2 id="tracing-using-aspects" xreflabel="tracing-using-aspects">
- <title>Tracing using aspects</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.)
- </para>
-
- <para>
- 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
- <classname>Trace</classname> classes have been known to
- have. <classname>Trace</classname> classes may be highly
- sophisticated, too, if the task of tracing the execution of a
- program demands it.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <sect3>
- <title>An Example Application</title>
-
- <para>
- Throughout this example we will use a simple application that
- contains only four classes. The application is about shapes. The
- <classname>TwoDShape</classname> class is the root of the shape
- hierarchy:
- </para>
-
- <programlisting><![CDATA[
- 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) + ") ");
- }
- }
- ]]></programlisting>
-
- <para>
- <classname>TwoDShape</classname> has two subclasses,
- <classname>Circle</classname> and <classname>Square</classname>:
- </para>
-
- <programlisting><![CDATA[
- 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());
- }
- }
- ]]></programlisting>
-
- <programlisting><![CDATA[
- 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());
- }
- }
- ]]></programlisting>
-
- <para>
- 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
- <filename><replaceable>InstallDir</replaceable>/examples</filename>
- and type:
- </para>
-
- <programlisting>
- ajc -argfile tracing/notrace.lst
- </programlisting>
-
- <para>To run the program, type</para>
-
- <programlisting>
- java tracing.ExampleMain
- </programlisting>
-
- <para>(we don't need anything special on the classpath since this is pure
- Java code). You should see the following output:</para>
-
- <programlisting><![CDATA[
- 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)
- ]]></programlisting>
-
- </sect3>
- <sect3>
- <title>Tracing—Version 1</title>
-
- <para>
- In a first attempt to insert tracing in this application, we will
- start by writing a <classname>Trace</classname> class that is
- exactly what we would write if we didn't have aspects. The
- implementation is in <filename>version1/Trace.java</filename>. Its
- public interface is:
- </para>
-
- <programlisting><![CDATA[
- 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) {...}
- }
- ]]></programlisting>
-
- <para>
- If we didn't have AspectJ, we would have to insert calls to
- <literal>traceEntry</literal> and <literal>traceExit</literal> in
- all methods and constructors we wanted to trace, and to initialize
- <literal>TRACELEVEL</literal> 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
- <filename>version1/TraceMyClasses.java</filename>):
- </para>
-
- <programlisting><![CDATA[
- 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());
- }
- }]]></programlisting>
-
- <para>
- 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.
- </para>
-
- <para>
- 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
- <literal>thisJoinPointStaticPart</literal>.
- </para>
-
- <para>
- To run this version of tracing, go to the directory
- <filename><replaceable>InstallDir</replaceable>/examples</filename>
- and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile tracing/tracev1.lst
- ]]></programlisting>
-
- <para>
- Running the main method of
- <classname>tracing.version1.TraceMyClasses</classname> should produce
- the output:
- </para>
-
- <programlisting><![CDATA[
- --> 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)
- ]]></programlisting>
-
- <para>
- When <filename>TraceMyClasses.java</filename> is not provided to
- <command>ajc</command>, the aspect does not have any affect on the
- system and the tracing is unplugged.
- </para>
- </sect3>
-
- <sect3>
- <title>Tracing—Version 2</title>
-
- <para>
- 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
- <literal>Trace—version1</literal> with the crosscutting
- support of <literal>TraceMyClasses—version1</literal>. We end
- up with a <literal>Trace</literal> aspect (found in
- <filename>version2/Trace.java</filename>) with the following public
- interface
- </para>
-
- <programlisting><![CDATA[
- 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();
- }
- ]]></programlisting>
-
- <para>
- In order to use it, we need to define our own subclass that knows
- about our application classes, in
- <filename>version2/TraceMyClasses.java</filename>:
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- }
- ]]></programlisting>
-
- <para>
- Notice that we've simply made the pointcut
- <literal>classes</literal>, that was an abstract pointcut in the
- super-aspect, concrete. To run this version of tracing, go to the
- directory <filename>examples</filename> and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile tracing/tracev2.lst
- ]]></programlisting>
-
- <para>
- 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
- <classname>tracing.version2.TraceMyClasses</classname> should
- output exactly the same trace information as that from version 1.
- </para>
-
- <para>
- The entire implementation of the new <classname>Trace</classname>
- class is:
- </para>
-
- <programlisting><![CDATA[
- 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());
- }
- }
- ]]></programlisting>
-
- <para>
- This version differs from version 1 in several subtle ways. The
- first thing to notice is that this <classname>Trace</classname>
- 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
- <classname>Trace</classname>) and the crosscutting usage of it (by
- the class <classname>TraceMyClasses</classname>). 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.</para>
-
- <para>
- A consequence of this is that there is no need for providing
- <literal>traceEntry</literal> and <literal>traceExit</literal> 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.
- </para>
-
- <para>
- 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 <classname>classes</classname> 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.
- </para>
- </sect3>
- </sect2>
- </sect1>
-
- <!-- ============================================================ -->
- <!-- ============================================================ -->
-
- <sect1 id="examples-production">
- <title>Production Aspects</title>
-
- <!-- ==================== -->
-
- <sect2 id="a-bean-aspect" xreflabel="a-bean-aspect"><!-- A Bean Aspect -->
- <title>A Bean Aspect</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/bean</filename>.)
- </para>
-
- <para>
- This example examines an aspect that makes Point objects into
- Java beans with bound properties.
- </para>
-
- <para>
- 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 <classname>Serializable</classname> or
- <classname>Externalizable</classname>. Any properties of the object
- that are to be treated as bean properties should be indicated by
- the presence of appropriate <literal>get</literal> and
- <literal>set</literal> methods whose names are
- <literal>get</literal><emphasis>property</emphasis> and
- <literal>set </literal><emphasis>property</emphasis> where
- <emphasis>property</emphasis> 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<emphasis>property</emphasis>
- methods.
- </para>
-
- <para>
- <classname>Point</classname> is a simple class representing points
- with rectangular coordinates. <classname>Point</classname> does not
- know anything about being a bean: there are set methods for
- <literal>x</literal> and <literal>y</literal> but they do not fire
- events, and the class is not serializable. Bound is an aspect that
- makes <classname>Point</classname> a serializable class and makes
- its <literal>get</literal> and <literal>set</literal> methods
- support the bound property protocol.
- </para>
-
- <sect3>
- <title>The <classname>Point</classname> class</title>
-
- <para>
- The <classname>Point</classname> class is a very simple class with
- trivial getters and setters, and a simple vector offset method.
- </para>
-
- <programlisting><![CDATA[
- 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() + ")" ;
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <classname>BoundPoint</classname> aspect</title>
-
- <para>
- The <classname>BoundPoint</classname> aspect is responsible for
- <literal>Point</literal>'s "beanness". The first thing it does is
- privately declare that each <literal>Point</literal> has a
- <literal>support</literal> field that holds reference to an
- instance of <classname>PropertyChangeSupport</classname>.
-
- <programlisting><![CDATA[
- private PropertyChangeSupport Point.support = new PropertyChangeSupport(this);
- ]]></programlisting>
-
- 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 <literal>this</literal>, an instance of
- <classname>Point</classname>. Since the <literal>support</literal>
- field is private declared in the aspect, only the code in the
- aspect can refer to it.
- </para>
-
- <para>
- The aspect also declares <literal>Point</literal>'s methods for
- registering and managing listeners for property change events,
- which delegate the work to the property change support object:
-
- <programlisting><![CDATA[
- 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);
- }
- ]]></programlisting>
- </para>
-
- <para>
- The aspect is also responsible for making sure
- <classname>Point</classname> implements the
- <classname>Serializable</classname> interface:
-
- <programlisting><![CDATA[
- declare parents: Point implements Serializable;
- ]]></programlisting>
-
- Implementing this interface in Java does not require any methods to
- be implemented. Serialization for <classname>Point</classname>
- objects is provided by the default serialization method.
- </para>
-
- <para>
- The <function>setters</function> pointcut picks out calls to the
- <literal>Point</literal>'s <literal>set</literal> methods: any
- method whose name begins with "<literal>set</literal>" and takes
- one parameter. The around advice on <literal>setters()</literal>
- stores the values of the <literal>X</literal> and
- <literal>Y</literal> properties, calls the original
- <literal>set</literal> method and then fires the appropriate
- property change event according to which set method was
- called.
- </para>
-
- <programlisting><![CDATA[
- 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));
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The Test Program</title>
-
- <para>
- The test program registers itself as a property change listener to
- a <literal>Point</literal> 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.
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- ...
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Compiling and Running the Example</title>
-
- <para>
- To compile and run this example, go to the examples directory and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile bean/files.lst
- java bean.Demo
- ]]></programlisting>
-
- </sect3>
- </sect2>
-
- <!-- ==================== -->
-
- <sect2 id="the-subject-observer-protocol" xreflabel="the-subject-observer-protocol">
- <title>The Subject/Observer Protocol</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/observer</filename>.)
- </para>
-
- <para>
- This demo illustrates how the Subject/Observer design pattern can be
- coded with aspects.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <sect3>
- <title>Generic Components</title>
-
- <para>
- The generic parts of the protocol are the interfaces
- <classname>Subject</classname> and <classname>Observer</classname>,
- and the abstract aspect
- <classname>SubjectObserverProtocol</classname>. The
- <classname>Subject</classname> interface is simple, containing
- methods to add, remove, and view <classname>Observer</classname>
- objects, and a method for getting data about state changes:
- </para>
-
- <programlisting><![CDATA[
- interface Subject {
- void addObserver(Observer obs);
- void removeObserver(Observer obs);
- Vector getObservers();
- Object getData();
- }
- ]]></programlisting>
-
- <para>
- The <classname>Observer</classname> interface is just as simple,
- with methods to set and get <classname>Subject</classname> objects,
- and a method to call when the subject gets updated.
- </para>
-
- <programlisting><![CDATA[
- interface Observer {
- void setSubject(Subject s);
- Subject getSubject();
- void update();
- }
- ]]></programlisting>
-
- <para>
- The <classname>SubjectObserverProtocol</classname> aspect contains
- within it all of the generic parts of the protocol, namely, how to
- fire the <classname>Observer</classname> objects' update methods
- when some state changes in a subject.
- </para>
-
- <programlisting><![CDATA[
- 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; }
-
- }
- ]]></programlisting>
-
- <para>
- 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 <literal>Observer</literal> can hold onto its <literal>Subject</literal>.
- </para>
- </sect3>
-
- <sect3>
- <title>Application Classes</title>
-
- <para>
- <classname>Button</classname> objects extend
- <classname>java.awt.Button</classname>, and all they do is make
- sure the <literal>void click()</literal> method is called whenever
- a button is clicked.
- </para>
-
- <programlisting><![CDATA[
- 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() {}
-
- }
- ]]></programlisting>
-
- <para>
- Note that this class knows nothing about being a Subject.
- </para>
-
- <para>
- ColorLabel objects are labels that support the void colorCycle()
- method. Again, they know nothing about being an observer.
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- }
- ]]></programlisting>
-
- <para>
- Finally, the <classname>SubjectObserverProtocolImpl</classname>
- implements the subject/observer protocol, with
- <classname>Button</classname> objects as subjects and
- <classname>ColorLabel</classname> objects as observers:
- </para>
-
- <programlisting><![CDATA[
- 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());
-
- }
- ]]></programlisting>
-
- <para>
- It does this by assuring that <classname>Button</classname> and
- <classname>ColorLabel</classname> implement the appropriate
- interfaces, declaring that they implement the methods required by
- those interfaces, and providing a definition for the abstract
- <literal>stateChanges</literal> pointcut. Now, every time a
- <classname>Button</classname> is clicked, all
- <classname>ColorLabel</classname> objects observing that button
- will <literal>colorCycle</literal>.
- </para>
- </sect3>
-
- <sect3>
- <title>Compiling and Running</title>
-
- <para>
- <classname>Demo</classname> 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 <filename>examples</filename> directory and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile observer/files.lst
- java observer.Demo
- ]]></programlisting>
-
- </sect3>
- </sect2>
-
- <!-- ==================== -->
-
- <sect2 id="a-simple-telecom-simulation" xreflabel="a-simple-telecom-simulation">
- <title>A Simple Telecom Simulation</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/telecom</filename>.)
- </para>
-
- <para>
- 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.
- </para>
-
- <sect3>
- <title>The Application</title>
-
- <para>
- 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.
- </para>
-
- <itemizedlist>
- <listitem>
- <para>
- The basic objects provide basic functionality to simulate
- customers, calls and connections (regular calls have one
- connection, conference calls have more than one).
- </para>
- </listitem>
-
- <listitem>
- <para>
- 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.
- </para>
- </listitem>
-
- <listitem>
- <para>
- 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.
- </para>
- </listitem>
- </itemizedlist>
-
- <para>
- The simulation of system has three configurations: basic, timing
- and billing. Programs for the three configurations are in classes
- <classname>BasicSimulation</classname>,
- <classname>TimingSimulation</classname> and
- <classname>BillingSimulation</classname>. These share a common
- superclass <classname>AbstractSimulation</classname>, which
- defines the method run with the simulation itself and the method
- wait used to simulate elapsed time.
- </para>
- </sect3>
-
- <sect3>
- <title>The Basic Objects</title>
-
- <para>
- The telecom simulation comprises the classes
- <classname>Customer</classname>, <classname>Call</classname> and
- the abstract class <classname>Connection</classname> with its two
- concrete subclasses <classname>Local</classname> and
- <classname>LongDistance</classname>. 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 <classname>Connection</classname>
- 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.
-
- <inlinemediaobject>
- <imageobject>
- <imagedata fileref="telecom.gif"/>
- </imageobject>
- </inlinemediaobject>
- </para>
-
- </sect3>
-
- <sect3>
- <title>The <classname>Customer</classname> class</title>
-
- <para>
- <classname>Customer</classname> has methods
- <literal>call</literal>, <literal>pickup</literal>,
- <literal>hangup</literal> and <literal>merge</literal> for
- managing calls.
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <classname>Call</classname> class</title>
-
- <para>
- 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 <classname>Local</classname> connection (see
- below), otherwise a <classname>LongDistance</classname> 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.
- </para>
- </sect3>
-
- <sect3>
- <title>The <classname>Connection</classname> class</title>
-
- <para>
- The class <classname>Connection</classname> models the physical
- details of establishing a connection between customers. It does
- this with a simple state machine (connections are initially
- <literal>PENDING</literal>, then <literal>COMPLETED</literal> and
- finally <literal>DROPPED</literal>). Messages are printed to the
- console so that the state of connections can be
- observed. Connection is an abstract class with two concrete
- subclasses: <classname>Local</classname> and
- <classname>LongDistance</classname>.
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
-
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <literal>Local</literal> and <literal>LongDistance</literal> classes</title>
-
- <para>
- The two kinds of connections supported by our simulation are
- <literal>Local</literal> and <literal>LongDistance</literal>
- connections.
- </para>
-
- <programlisting><![CDATA[
- class Local extends Connection {
- Local(Customer a, Customer b) {
- super(a, b);
- System.out.println("[new local connection from " +
- a + " to " + b + "]");
- }
- }
- ]]></programlisting>
-
- <programlisting><![CDATA[
- class LongDistance extends Connection {
- LongDistance(Customer a, Customer b) {
- super(a, b);
- System.out.println("[new long distance connection from " +
- a + " to " + b + "]");
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Compiling and Running the Basic Simulation</title>
-
- <para>
- The source files for the basic system are listed in the file
- <filename>basic.lst</filename>. To build and run the basic system,
- in a shell window, type these commands:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile telecom/basic.lst
- java telecom.BasicSimulation
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The Timing aspect</title>
-
- <para>
- The <classname>Timing</classname> aspect keeps track of total
- connection time for each <classname>Customer</classname> by
- starting and stopping a timer associated with each connection. It
- uses some helper classes:
- </para>
-
- <sect4>
- <title>The <classname>Timer</classname> class</title>
-
- <para>
- A <classname>Timer</classname> object simply records the current
- time when it is started and stopped, and returns their difference
- when asked for the elapsed time. The aspect
- <classname>TimerLog</classname> (below) can be used to cause the
- start and stop times to be printed to standard output.
- </para>
-
- <programlisting><![CDATA[
- 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;
- }
- }
- ]]></programlisting>
-
- </sect4>
- </sect3>
-
- <sect3>
- <title>The <classname>TimerLog</classname> aspect</title>
-
- <para>
- The <classname>TimerLog</classname> aspect can be included in a
- build to get the timer to announce when it is started and
- stopped.
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <classname>Timing</classname> aspect</title>
-
- <para>
- The <classname>Timing</classname> aspect is declares an
- inter-type field <literal>totalConnectTime</literal> for
- <classname>Customer</classname> to store the accumulated connection
- time per <classname>Customer</classname>. It also declares that
- each <classname>Connection</classname> object has a timer.
-
- <programlisting><![CDATA[
- public long Customer.totalConnectTime = 0;
- private Timer Connection.timer = new Timer();
- ]]></programlisting>
-
- 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 <literal>endTiming</literal> is defined so that it can
- be used by the <classname>Billing</classname> aspect.
- </para>
-
- <programlisting><![CDATA[
- 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();
- }
- }]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>The <literal>Billing</literal> aspect</title>
-
- <para>
- The Billing system adds billing functionality to the telecom
- application on top of timing.
- </para>
-
- <para>
- The <classname>Billing</classname> aspect declares that each
- <classname>Connection</classname> has a <literal>payer</literal>
- 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 <literal>callRate</literal> of
- <classname>Connection</classname> 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
- <literal>Timing.endTiming</literal> does this, and
- <classname>Billing</classname> is declared to be more precedent
- than <classname>Timing</classname> to make sure that this advice
- runs after <classname>Timing</classname>'s advice on the same join
- point. Finally, it declares inter-type methods and fields for
- <classname>Customer</classname> to handle the
- <literal>totalCharge</literal>.
- </para>
-
- <programlisting><![CDATA[
- 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;
- }
- }
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Accessing the inter-type state</title>
-
- <para>
- Both the aspects <classname>Timing</classname> and
- <classname>Billing</classname> 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.
- </para>
-
- <para>
- Take a look at the file
- <filename>TimingSimulation.java</filename>. The most important
- method of this class is the method
- <filename>report(Customer)</filename>, which is used in the method
- run of the superclass
- <classname>AbstractSimulation</classname>. This method is intended
- to print out the status of the customer, with respect to the
- <classname>Timing</classname> feature.
- </para>
-
- <programlisting><![CDATA[
- protected void report(Customer c){
- Timing t = Timing.aspectOf();
- System.out.println(c + " spent " + t.getTotalConnectTime(c));
- }
- ]]></programlisting>
- </sect3>
-
- <sect3>
- <title>Compiling and Running</title>
-
- <para>
- 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:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile telecom/timing.lst
- java telecom.TimingSimulation
- ]]></programlisting>
-
- <para>
- To build and run the application with the timing and billing
- features, go to the directory examples and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile telecom/billing.lst
- java telecom.BillingSimulation
- ]]></programlisting>
-
- </sect3>
-
- <sect3>
- <title>Discussion</title>
-
- <para>
- There are some explicit dependencies between the aspects Billing
- and Timing:
-
- <itemizedlist>
- <listitem>
- <para>
- 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.
- </para>
- </listitem>
-
- <listitem>
- <para>
- Billing uses the pointcut Timing.endTiming.
- </para>
- </listitem>
-
- <listitem>
- <para>
- Billing needs access to the timer associated with a connection.
- </para>
- </listitem>
- </itemizedlist>
- </para>
- </sect3>
- </sect2>
- </sect1>
-
- <!-- ============================================================ -->
- <!-- ============================================================ -->
-
- <sect1 id="examples-reusable">
- <title>Reusable Aspects</title>
-
- <sect2 id="tracing-using-aspects-revisited" xreflabel="tracing-using-aspects-revisited">
- <title>Tracing using Aspects, Revisited</title>
-
- <para>
- (The code for this example is in
- <filename><replaceable>InstallDir</replaceable>/examples/tracing</filename>.)
- </para>
-
- <sect3>
- <title>Tracing—Version 3</title>
-
- <para>
- 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.
- </para>
-
- <para>
- 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 <literal>traceEntry</literal>
- and <literal>traceExit</literal> as it was before,
- </para>
-
- <programlisting><![CDATA[
- public static void traceEntry(String str);
- public static void traceExit(String str);
- ]]></programlisting>
-
- <para>
- 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:
- </para>
-
- <programlisting><![CDATA[
- Trace.traceEntry("Square.distance in " + toString());
- ]]></programlisting>
-
- <para>
- Another way is to enforce the requirement with a second argument
- in the trace operations, e.g.
- </para>
-
- <programlisting><![CDATA[
- public static void traceEntry(String str, Object obj);
- public static void traceExit(String str, Object obj);
- ]]></programlisting>
-
- <para>
- 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:
- </para>
-
- <programlisting><![CDATA[
- Trace.traceEntry("Square.distance", this);
- ]]></programlisting>
-
- <para>
- 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!
- </para>
-
- <para>
- Here's another advantage of doing tracing with an aspect. We've
- already seen that in version 2 <literal>traceEntry</literal> and
- <literal>traceExit</literal> are not publicly exposed. So
- changing their interfaces, or the way they are used, has only a
- small effect inside the <classname>Trace</classname>
- class. Here's a partial view at the implementation of
- <classname>Trace</classname>, version 3. The differences with
- respect to version 2 are stressed in the comments:
- </para>
-
- <programlisting><![CDATA[
- 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);
- }
- }
- ]]></programlisting>
-
- <para>
- As you can see, we decided to apply the first design by preserving
- the interface of the methods <literal>traceEntry</literal> and
- <literal>traceExit</literal>. But it doesn't matter—we could
- as easily have applied the second design (the code in the directory
- <filename>examples/tracing/version3</filename> has the second
- design). The point is that the effects of this change in the
- tracing requirements are limited to the
- <classname>Trace</classname> aspect class.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- Moreover, we had to exclude the execution join point of the method
- <filename>toString</filename> from the <literal>methods</literal>
- pointcut. The problem here is that <literal>toString</literal> 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!
- </para>
-
- <para>
- 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
- </para>
-
- <programlisting><![CDATA[
- && !cflow(execution(String toString()))
- ]]></programlisting>
-
- <para>
- excluding both the execution of toString methods and all join
- points under that execution.
- </para>
-
- <para>
- In summary, to implement the change in the tracing requirements we
- had to make a couple of changes in the implementation of the
- <classname>Trace</classname> 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.
- </para>
-
- <para>
- Finally, to run this version of tracing, go to the directory
- <filename>examples</filename> and type:
- </para>
-
- <programlisting><![CDATA[
- ajc -argfile tracing/tracev3.lst
- ]]></programlisting>
-
- <para>
- The file tracev3.lst lists the application classes as well as this
- version of the files <filename>Trace.java</filename> and
- <filename>TraceMyClasses.java</filename>. To run the program, type
- </para>
-
- <programlisting><![CDATA[
- java tracing.version3.TraceMyClasses
- ]]></programlisting>
-
- <para>The output should be:</para>
-
- <programlisting><![CDATA[
- --> 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)
- ]]></programlisting>
-
- </sect3>
- </sect2>
- </sect1>
- </chapter>
|