Introduction to GluonJ

Shigeru Chiba

Table of Contents

1. Why do you use GluonJ?
2. Writing a glue
3. Refinement
4. Pointcut and Advice


1. Why do you use GluonJ?

GluonJ is a simple AOP (Aspect-Oriented Programming) system. It provides simple AOP constructs within the confines of the regular Java syntax.

There are a number of applications of AOP. For example, AOP is useful for writing test code. Suppose that we are writing the following program:

package demo;

import java.math.BigDecimal;

public class Bank {
    public BigDecimal transfer(Account src, Account dest,
                               BigDecimal amount) {
        dest.deposit(amount);
        return src.withdraw(amount);
    }
}
package demo;

import java.math.BigDecimal;

public class Account {
    private BigDecimal balance;

    public BigDecimal deposit(BigDecimal amount) {
        return balance.add(amount);
    }

    public BigDecimal withdraw(BigDecimal amount) {
        return balance;         // incomplete!
    }
}

The definition of the Account class is obviously not complete. The value of balance is never initialized and the withdraw method does nothing. However, we sometimes have to write a test program for a program including such an incomplete class. The following test program examines the implementation of the transfer method in the Bank class.

package demo;

import junit.framework.TestCase;
import java.math.BigDecimal;

public class BankTest extends TestCase {
    public void testTransfer() {
        Account mine = new Account();
        Account yours = new Account();
        BigDecimal pay = new BigDecimal(20000);
        BigDecimal balance = new BigDecimal(10000);
        Bank bank = new Bank();
        assertEquals(balance, bank.transfer(yours, mine, pay));
    }
}

Of course, this test program fails if it is run. The transfer method calls the withdraw method in Account but its implementation is not complete.

Since this failure of the test program is irrelevant to the impelementation of the transfer method, let us fix a problem and make the test program succeeed. This test program should successfully finish unless the transfer method has a bug.

Fixing a problem is easy if you use GluonJ. What we have to do is only writing the following glue class:

package demo;

import javassist.gluonj.*;
import java.math.BigDecimal;

@Glue public class Mock {
    @Refine("demo.Account")
    static class Account {
        private BigDecimal balance = new BigDecimal(30000);

        public BigDecimal withdraw(BigDecimal amount) {
            return balance.subtract(amount);
        }
    }
}

If all the programs above are run together, the test program BankTest finishes successfully. The glue class injects the initial value 30000 into the balance field as a dependency injection framework does. It also substitutes a tentative implementation for the original one of the withdraw method (a complete implementation should throw an exception when the amount is too large). Further details will be discussed later in this document.

Note that the glue class shown above is still useful even after the implementation of Account becomes complete. To perform a unit test, the result of the test code must be independent of other components, modules, or classes. During a test, the glue class above makes the Bank class independent of the implementation of the Account class. Even after the withdraw method is fully implemented, the glue class switches it back to the temporary implementation for the unit test.

Running all the programs together is simple. If you are using the JDK 1.5 or later, give the following command-line option:

-javaagent:gluonj.jar=demo.Mock

We assume that gluonj.jar is in the current directory. If you are using Eclipse, this command-line option is given to the JVM as a VM argument, which can be specified by the Launch Configurations dialog.

2. Writing a glue

A glue is a collection of various extensions to existing classes. It is described by using a class annotated by @Glue (@Glue class). The extensions are described in the form of either a pointcut-advice or a refinement. A @Glue class can contain any number of poitncut-advices and refinements. A pointcut-advice is a field of type Pointcut declared in a @Glue class. This field must be annotated by @Before, @After, or @Around. A refinement is a static nested class included in a @Glue class. We call it a @Refine class. It corresponds to an intertype declaration of AspectJ.

The following is an example of @Glue class:

package test;

import javassist.gluonj.*;

@Glue class Logging {
    @Before("{ System.out.println(`call!`); }")
    Pointcut pc = Pcd.call("test.Hello#say(..)");

    @Refine("test.Hello")
    static class Afternoon {
        private String header() {
            return "Good afternoon, ";
        }
    }
}

This @Glue class extends the test.Hello class shown below:

package test;

public class Hello {
    private String header() {
        return "Good morning, ";
    }

    public void say(String toWhom) {
        System.out.println(header() + toWhom);
    }

    public static void main(String[] args) {
        new Hello().say("Bill");
    }
}

If the Hello class is run without the @Glue class above, then the output will be:

Good morning, Bill

On the other hand, if the Hello class is run with the @Glue, then the output will be changed into:

call!
Good afternoon, Bill

The @Before pointcut-advice prints "call!" just before the say method is called. The @Refine class redefines the header method so that it will return "Good afternoon, ". For more details, please see the following sections below.

Weaving

To apply a @Glue class to other classes, we must perform weaving, which is post-compilation program transformation. GluonJ supports two types of weaving: off-line weaving and load-time weaving.

Ant task

The first type of weaving is off-line weaving. We use an ant task for transforming compiled class files according to a given @Glue class. The following is an example of build.xml file:

<?xml version="1.0"?>
<project name="hello" basedir=".">
    <taskdef name="weave" classname="javassist.gluonj.ant.taskdefs.Weave">
        <classpath>
            <pathelement location="./gluonj.jar"/>
        </classpath>
    </taskdef>

    <target name="weave">
        <weave glue="test.Logging" destdir="./out" debug="false" >
            <classpath>
                <pathelement path="./classes"/>
            </classpath>
            <fileset dir="./classes" includes="test/**/*" />
        </weave>
    </target>
</project>

This build.xml file assumes that gluonj.jar is in the current directory. It also assumes that the class files produced by the Java compiler are in the ./classes directory and they are saved in the ./out directory after the transformation. The @Glue class is test.Logging. It transforms class files specified by the fileset element. In the example above, all class files in the ./classes/test (classes whose package names start with test) are transformed.

The weave task has the debug attribute. If it is true, GluonJ will print detailed log messages. This attribute is optional. The default value is false.

Command line

Off-line weaving can be done also through command-line interface. For example,

java -jar gluonj.jar test.Logging test/Hello.class

Before running the java command above, we must move to the ./classes directory. Class files must be in the current directory. Their path names must be ./test/Logging.class and ./test/Hello.class. The second argument test.Logging is the fully-qualified name of a @Glue class. If multiple class files are transformed, they must be given as the third, forth, ... arguments following test.Logging. These arguments are the path names of those class files (not class names).

If no arguments are given, gluonj.jar prints the version number and the copyright notices of GluonJ.

java -jar gluonj.jar

Load-time weaving

The other type of weaving is load-time weaving. We can transform class files when the Java virtual machine (JVM) loads them. To do that, the -javaagent command-line option must be given to the JVM. For example,

java -javaagent:gluonj.jar=test.Logging -cp "./classes" test.Hello

Again, we assume that gluonj.jar is in the current directory and class files are in the ./classes directory. The @Glue class is test.Logging. Note that -javaagent:... is a VM argument. If we execute the command above by an ant task, the task will be:

<java fork="true" classname="test.Hello">
    <classpath>
        <pathelement path="./classes"/>
    </classpath>
    <jvmarg value="-javaagent:./gluonj.jar=test.Logging"/>
</java>

If you want to see detailed log messages, you must specify the debug option. For example,

java -javaagent:gluonj.jar=test.Logging,debug -cp "./classes" test.Hello

Note that you must not insert an space between the comma and debug.

Weave according to multiple @Glue classes

In the case of either off-line weaving or load-time weaving, only a single @Glue class can be specified. If you want to apply multiple @Glue classes, you must define a @Glue class including other @Glue classes.

For example, the following @Glue class includes two other @Glue classes:

package test;

import javassist.gluonj.*;

@Glue class AllGlues {
    @Include Logging glue0;
    @Include Tracing glue1;
}

The @Glue classes test.Logging and test.Tracing are included in the AllGlues as child @Glue classes.

The @Include specifies another @Glue class included. If a field declaration is annotated by @Include, then the type of the field must be a @Glue class, which will be included as a child @Glue class. The fields annotated by @Include are sorted by their names in the dictionary order and the type of a former field in that order has a higher priority when it is used for weaving as a @Glue class. The parent @Glue class, which contains @Include fields, has the lowest priority if it also contains a refinement or a pointcut-advice as a member.

3. Refinement

@Refine specifies a refinement on an existing class. In other words, it extends the definition of an existing class. Unlike the inheritance and mixin mechanisms, however, @Refine directly modifies the original class definition. @Refine corresponds to the intertype declaration of AspectJ.

Suppose that the following class has been declared:

package test;
public class Person {
    public String name;
    public void greet() {
        System.out.println("I'm " + name);
    }
}

Let us extend this class definition for test.Person:

@Glue class SayHello {
    @Refine("test.Person")
    static class Diff {
        public String message = "Hello";
        public void greet() {
            System.out.println(message);
        }
    }
}

This glue includes a static nested class named Diff, which is annotated by @Refine. The name of that nested class does not matter. Other names such as P and Expand are also fine.

This nested @Refine class appends a new field message to the test.Person class. Also, the greet method declared in the nested class substitutes the greet method in the original test.Person class. The target class that a field and a method are appended to is specified by the parameter to @Refine. The name of the target class must be a fully qualified name.

The resulting definition is the follow:

package test;
public class Person {
    public String name;
    private String message = "Hello";
    public void greet() {
        System.out.println(message);
    }
}

Although the SayHello shown above contains only one @Refine class, a @Glue class can contain any number of @Refine classes as its member.

A @Refine class can be declared as not a nested class but a top-level class. The following program is equivalent to the @Glue class SayHello shown above:

@Glue class SayHello {
    @Refine("test.Person") Diff refines;
}

public class Diff {
    public String message = "Hello";
    public void greet() {
        System.out.println(message);
    }
}

The field declaration annotated by @Refine specifies a refinement. The type of the declared field must be a refinement class. This refinement class is used to extend the class specified by the argument to @Refine. In the case above, the test.Person class is modified according to the Diff class. The name of the field does not matter. Any name can be given to it.

Add a new method

A @Refine class can append a new method to its original class:

@Glue class Cheerful {
    @Refine("test.Person")
    static class Diff implements CheerfulPerson {
        public void sayHi(String toWhom) {
            System.out.println("Hi " + toWhom);
        }
    }
}

public interface CheerfulPerson {
    void sayHi(String toWhom);
}

This @Refine class appends the sayHi method to the test.Person class. Since it implements the CheerfulPerson interface, the test.Person class is extended to implement the CheerfulPerson interface.

Implementing CheerfulPerson is necessary to call the added method sayHi:

Person p = new Person();
((CheerfulPerson)p).sayHi("Stieve");

Since the original definition of the Person class does not include the sayHi method, the code fragment above cannot be compiled without a type cast to CheerfulPerson. Note that the sayHi method will not be added until the glue is woven after compilation.

If an added method is static, the technique above does not work. A different technique must be used.

@Glue class Cheerful2 {
    @Refine("test.Person")
    static class NewPerson {
        public static void sayHi(String toWhom) {
            System.out.println("Hi " + toWhom);
        }
    }

    @Refine("test.Skit")
    static class NewSkit {
        public static void main(String[] args) {
            NewPerson.sayHi("Paul");
        }
    }
}

Note that the main method in NewSkit calls a static method declared in NewPerson, which is not an original class but a @Refine class. However, this call is interpreted as a call to the sayHi method added to the test.Person class.

To weave a glue, GluonJ copies a method in a @Refine class to its original class. When copying, GluonJ substitutes original class names for all occurrences of @Refine class names in the method definition. In the case above, GluonJ substitutes test.Person for NewPerson when it copies the main method from NewSkit to test.Skit. The class names substituted for are the names of all @Refine classes belonging to the same @Glue class.

Accesses to a field in a refined class

A method in a @Refine class can access fields in its target class. For example,

@Glue class Setter {
    @Refine("test.Person")
    static class Diff implements PersonSetter {
        public String name;
        public void setName(String newName) {
            name = newName;
        }
    }
}

public interface PersonSetter {
    void setName(String newName);
}

The above @Refine class adds a setName method to the test.Person class. The name field declared in the @Refine class represents the same name field in the test.Person class. If a @Refine class and the refined class include fields with the same name, those fields are identical. In the case above, the resulting class is:

package test;
public class Person implements PersonSetter {
    public String name;
    public void greet() {
        System.out.println("I'm " + name);
    }
    public void setName(String newName) {
        name = newName;
    }
}

Since the above @Refine class implements the PersonSetter interface, the resulting class also implements that interface.

If a field in a @Refine class has an initial value, that initial value overrides the initial value of the corresponding field in the refined class. For example,

@Glue class Init {
    @Refine("test.Person")
    static class Diff {
        public String name = "Joe";
    }
}

After weaving this glue, the initial value of the name field in test.Person becomes "Joe".

Accesses to a method in a refined class

A method in a @Refine class can access methods in its refined class. If a @Refine class declares an abstract method with the same name as a method in its refined class, that abstract method represents the corresponding method in the refined class.

@Glue class Sociable {
    @Refine("test.Person")
    static abstract class Diff implements CheerfulPerson {
        public abstract void greet();
        public void sayHi(String toWhom) {
            System.out.println("Hi " + whom);
            greet();
        }
    }
}

The sayHi method calls the greet method declared in the refined class. Note that the @Refine class declares an abstract method named greet. Furthermore, the @Refine class is also abstract since only an abstract class can declare an abstract method in Java.

If an abstract method in an @Refine class has a name starting with orig_, it has a special meaning.

@Glue class Greeting {
    @Refine("test.Person")
    static abstract class Diff {
        public abstract void orig_greet();
        public void greet() {
            System.out.println("Hello");
            orig_greet();
        }
    }
}

This @Refine class overrides the greet method in the refined class. However, the overridden greet method is called from the overriding method by the orig_greet method. The abstract method orig_greet represents the original greet method declared in the refined class.

Declaring an abstract orig_... method enables a call to an overridden method. However, this technique does not work for calling an overridden static method since a static method cannot be abstract in Java.

As for a static method, we must use a different technique. For example,

package test;
public class Greeting {
    public static String say(Person p) {
        return "Hi, " + p.name;
    }
}

Let us modify the say method:

@Glue class Verbose {
    @Refine("test.Greeting")
    static class Diff {
        @Abstract public static String orig_say(Person p) {
            return null;
        }
        public static String say(Person p) {
            return orig_say(p) + ". How are you?";
        }
    }
}

The overridden say method is called by orig_say. If p.name is "Bill", then the overriding say method returns "Hi, Bill. How are you?".

The declaration of a static orig_... method must be annotated with @Abstract. Since the body of that method is ignored, it can be any statement if it is valid in Java. Normally, it is a return statement that returns null or 0. If the return type is void, then the method body can be empty.

Using this

When the special variable this is used in a method in a @Refine class, you must consider its type:

import test.Person;
import static javassist.gluonj.Gluon.$refine;

@Glue class UseThis {
    @Refine("test.Person")
    static class Diff {
        public Person self() {
            return (Person)$refine(this);
        }
    }
}

Since the type of this is not Person but Diff, the self method must perform type cast. GluonJ provides a static method named $refine for this purpose. The cast expression for this should take this form:

( <target type> )$refine(this)

Summary of @Refine

In summary, each member in a @Refine class is copied to its target class as following:

  • A field in a @Refine class:

    • is appended to the refined class
      if it has a different name from the other fields in the refined class. Here, the other fields include one inherited from a super class.

    • is merged with a field in the refined class
      if they have the same name and type.

      • If the field in the @Refine class has an initial value,
        that initial value overwrites the initial value of the field in the refined class.

        Since the new initial value is assigned just before a constructor finishes, an original initial value might be used during the execution of the constructor body.

      • The annotations of the field is also merged.

  • A method in a @Refine class:

    • is appended to the refined class
      if it has a different name from the other methods in the refined class.

      • The appended method can be called through an interface, which must be also appended by the @Refine class.

      • If the method in the @Refine class is abstract and it has a name starting with orig_, such as orig_XXX, then it refers to the method named XXX in the refined class.

    • is merged with a method in the refined class
      if they have the same name and signature.

      • The method in the @Refine class overrides the method in the refined class
        unless the former method is abstract.

      • Otherwise, if the method in the @Refine class is abstract, then it is absorbed in the method in the refined class.

If you add a new method, you must also add an interface for accessing that added method. An interface implemented by a @Refine class is added to the refined class.

  • Interfaces implemented by a @Refine class
    • are also implemented by the refined class (if not implemented yet). The refined class does not inherit from the super class of the @Refine class.

@Refine can be also used for extending an interface. If a target is an interface, a @Refine class must be also an interface type. The rule of copying methods is:

  • A method in a @Refine interface
    • is copied to the refined interface (if it is not declared in the refined interface).

An interface that a @Refine interface extends is added to the list of the super interfaces of the refined interface.

4. Pointcut and Advice

Another kind of member of a @Glue class is a pointcut-advice, which is a field annotated by @Before, @After, or @Around. Unlike AspectJ, a pointcut and an advice are not separated. A @Glue class can contain any number of pointcut-advices as a @Refine class.

A field representing a pointcut-advice, which we below call a pointcut field, must have the type Pointcut. A @Glue class can contain any number of fields of the type Pointcut and it can annotate only some of them by @Before etc. If fields of the Pointcut type are not annotated, then they are not treated as pointcut fields.

A typical pointcut field is like this:

@Before("{ System.out.println(`call!`); }")
Pointcut pc = Pcd.call("test.Hello#say(..)");

Note that a back-quote ` used in an argument to @Before is interpreted as an escaped double quote \". This notation is provided for convenience.

The declaration above specifies that the following block statement:

{ System.out.println("call!"); }

is executed when a say method in test.Hello is called. Since the annotation is @Before, the statement is executed just before the method body starts running, after all the arguments to the method are evaluated. The block statement above is below called an advice body.

A Pointcut object specifies when a block statement passed to @Before is executed. The Pcd (Pointcut Designator) class provides factory methods to create a Pointcut object. The call method returns a Pointcut object specifies the time when a method is called. The String argument to call:

test.Hello#say(..)

specifies a call to say declared in test.Hello. # is a separator between a class name and a method name. The parameter types of the say method are not specified since (..) is given. The argument above specifies calls to say(), say(int), say(String,int),...

Note: You might be wondering that an advice body is given as a parameter to @Before. In fact, other AOP systems have developers give a pointcut expression as a parameter to @Before:

@Before("call(test.Hello#say(..))")
public void advice() {
    System.out.println(`call!`);
}

This is because the designers of GluonJ think an advice body should be short, normally one statement for calling on another object. A @Glue is not a component implementing a crosscutting concern. It is really a glue bonding such a crosscutting component a.k.a. an aspect to other components. Hence an advice body should be a short glue code just bonding them. The designers believe that this design will rescue developers from being puzzled about complicated rules to implicitly instantiate an aspect.

Pointcut designators

GluonJ can deal with several kinds of pointcuts. The following is a list of factory methods in Pcd and Pointcut:

  • Pointcut call(String methodPattern)
    When a method (or a constructor) specified by methodPattern is called.

  • Pointcut get(String fieldPattern)
    When the value of a field specified by fieldPattern is obtained.

  • Pointcut set(String fieldPattern)
    When a new value is assigned to a field specified by fieldPattern.

  • Pointcut within(String classPattern)
    When a thread of control is within a method immediately declared in the class specified by classPattern.

  • Pointcut within(String methodPattern)
    When a thread of control is within the body of a method specified by methodPattern.

  • Pointcut annotate(String annotationPattern)
    When a method or a field with an annotation specified by annotationPattern is accessed.

  • Pointcut when(String javaExpression)
    While javaExpression is true. This corresponds to AspectJ's if pointcut designator.

  • Pointcut cflow(String methodPattern)
    While a method specified by methodPattern is running.

The pointcuts except call, get, and set are usually used with one of these three poitncuts call, get, or set.

Composition

These pointcuts can be composed with others by using .and or .or. For example,

Pointcut pc = Pcd.call("test.Hello#say(..)").and.within("test.Main");

The created Pointcut object specifies the time when the say method in test.Hello is called from a method declared in the test.Main class. Multiple .and and .or can be used in a single expression. For example,

Pointcut pc = Pcd.call("test.Hello#say(..)").and.within("test.Main")
                 .or.call("test.Hello#greet());

This specifies the time when the say method is called within the test.Main class or when the greet method is called within any class.

.and has a higher precedence than .or. If you want to change this precedence rule, you must use the expr method in Pcd and Pointcut. This method works as parentheses.

Pointcut pc = Pcd.expr(Pcd.call("test.Hello#say(..)").or.call("test.Hello#greet()"))
                 .and.within("test.Main")

This specifies the time when either say or greet is called within test.Main. The two call pointcuts are grouped by the expr method.

The expr method is available in the middle of an expression:

Pointcut pc = Pcd.within("test.Main")
                 .and.expr(Pcd.call("test.Hello#say(..)")
                           .or.call("test.Hello#greet()"))

You can also split the declaration above into two:

Pointcut pc0 = Pcd.call("test.Hello#say(..)").or.call("test.Hello#greet());

@Before("{ System.out.println(`call!`); }")
Pointcut pc = Pcd.expr(pc0).and.within("test.Main");

This also creates the same Pointcut object. Since the declaration of pc0 is not annotated, pc0 is not treated as a pointcut field. It is only used as a sort of temporary variable.

For negation, GluonJ provides .not, which can be placed after Pcd, .and, or .or. .not has the highest precedence. For example,

Pointcut pc = Pcd.call("test.Hello#say(..)").and.not.within("test.Main");
Pointcut pc2 = Pcd.not.within("test.Main").and.call("test.Hello#say(..)");

Both the two pointcuts above specify the time when the say method is called within any class except test.Main.

Patterns

call takes a method pattern. It is a concatenation of a class name, a method name, and a list of parameter types. The class name and the method name are separated by #. For example,

test.Point#move(int, int)

represents a move method declared in test.Point. It receives two int parameters. The class name must be a fully qualified name, which includes a package name. A class name and a method name can include a wild card *. For example, test.* means any class in the test package. A list of parameter types cannot include a wild card *. To specify any type, (..) is used.

A method pattern may match a constructor if the method name in the pattern is new. For example,

test.Point#new(int, int)

This pattern matches a new expression with two int arguments.

A field pattern taken by get and set is similar to a method pattern. It is a concatenation of a class name and a field name. For example,

test.Point#xpos

represents a xpos field declared in test.Point. A class name and a field name can include a wild card * as well.

Finally, a class pattern is a fully-qualified class name. It can include a wild card *. An annotation pattern is a fully-qualified class name starting with/without @, for example, @test.Change. It can include a wild card *.

Advice body

The declaration of a pointcut field is annotated by either @Before, @After, or @Around. If @Before is used, the advice body passed to that annotation as an argument is executed just before the time specified by the pointcut field. If @After is used, the advice body is executed just after the time specified by the pointcut field. For example, if the pointcut field specifies the time when a method is called, then the advice body is executed just after a return statement in the body of the called method is executed.

  • @Before(adviceBody)
    The given advice body is executed before the time specified by the pointcut field annotated by this.

  • @After(adviceBody)
    The given advice body is executed after the time specified by the pointcut field annotated by this.

  • @Around(adviceBody)
    The given advice body is executed instead of the computation specified by the pointcut field annotated by this.

@Around has a special meaning. If it is used, the given advice body is executed instead of the code fragment specified by the pointcut field annotated by that @Around. For example,

@Around("{ System.out.println(`call!`); }")
Pointcut pc = Pcd.call("test.Hello#say(..)")
                 .and.within("test.Main");

If a method in test.Main class attempts to invoke the say method, that method invocation is intercepted. Then the body of the say method is not executed but instead the advice body given to the @Around is executed. In other words, the advice body is substituted for the call to say from a method in test.Main.

Runtime Contexts

An advice body is a block statement surrounded by {} or a single statement that ends with ; (a semicolon). It is written in regular Java but several special variables are available in an advice body.

$proceed

In an advice body given to @Around, a special form $proceed is available. It represents the original computation that the advice body is substituted for.

@Around("{ System.out.println(`call!`); $_ = $proceed($$); }")
Pointcut pc = Pcd.call("test.Hello#say(..)");

This advice body first prints a message and then invokes the say method in test.Hello since the method invocation is the original computation that the advice body is substituted for. $proceed is used as a method name. If it is called, it executes the original computation, that is, the say method. $$ represents the original set of arguments given to the computation, that is, the say method.

$_ is the special variable that the resulting value must be stored. Since @Around substitutes the computation specified by a pointcut field, its advice body must set $_ to an appropriate value before finishing. The type of $_ is the same type as the resulting value of the original computation. If the return type of the originally called method is void, then the value stored in $_ is ignored. Otherwise, the value stored in $_ is used as the result of the advice body.

For example, suppose that the say method returns a value of the String type:

String s = hello.say();

If the pointcut field:

@Around("{ $_ = "OK"; }")
Pointcut pc = Pcd.call("test.Hello#say(..)");

is applied to the call to say, then the value stored in $_, which is "OK", is assigned to the variable s. This is because the advice body is substituted for the call to say. The say method is never executed.

The meanings of $proceed, $$, and $_, depend on the kind of the original computation. However, the following statement:

$_ = $proceed($$);

executes the original computation whatever is the original computation.

If the original computation is a method call, then $proceed executes that method call. It takes the same set of parameters as the original method and returns the same type of value. $$ represents the list of the original arguments. The type of $_ is the return type of the original method. The value stored in $_ is used as the result of the advice body.

If the original computation is a field read, $proceed executes that operation. It takes no parameter and returns the value of the field. $$ represents an empty list. The type of $_ is the same as the field type. The value stored in $_ is used as the result of the field access instead of the value of the accessed field.

If the original computation is a field write, $proceed changes the value of the field. It takes a new value as a parameter and returns no value (i.e. void). $$ represents the value that the original computation attempts to assign. $_ is still available but the value stored in $_ is ignored.

$0, $1, $2, ...

Although $$ represents a list of actual arguments given to original computation, the value of an individual argument is also accessible through a special variable. If original computation is a method call, then $1 represents the first argument, $2 represents the second argument, and so on. $0 represents the target object that the method is called on. To obtain a reference to the caller/accessor object, i.e. an issuer of original computation, this should be used.

A value assigned to $1, $2, ... is reflected on $$. For example,

@Around("{ $1 = "Joe"; $_ = $proceed($$); }")
Pointcut pc = Pcd.call("test.Hello#sayHi(String)");

After this advice body is executed, the value stored in $_ is the value returned from the sayHi method called with "Joe". Thus, the effects of the advice body above is equivalent to:

@Around("{ $_ = $proceed("Joe"); }")
Pointcut pc = Pcd.call("test.Hello#sayHi(String)");

$0, $1, $2, ... are available within an advice body given to @Before or @After.

Alias

It is possible to define an alias of a special variable. For example,

@Before("{ System.out.println(msg + ` ` + callee); }")
Pointcut pc = Pcd.define("msg", "$1").define("callee", "$0")
                 .call("test.Hello#sayHi(String)");

The define method defines an alias. A call to define must follow Pcd. or another call to define. In the case above, two aliases are defined. One is msg, which is an alias of $1, and the other is callee, which is an alias of $0. These two aliases are available within the advice body given to @Before.

Logging aspect

A logging aspect is the Hello World of AOP languages. We here present an example of logging aspect.

Writing a logging aspect in GluonJ is as simple as in other AOP languages. For example, the following @Glue is for printing a log message just before the testTransfer method in the demo.BankTest class calls the transfer method in the demo.Bank class. We have already shown the the demo.BankTest class and the demo.Bank class in Section 1.

package demo;

import javassist.gluonj.*;

@Glue public class Logging {
    @Before("{ System.out.println(`transfer: ` + $3 + ` ` + balance); }")
    Pointcut pc = Pcd.call("demo.Bank#transfer(..)")
                     .and.within("demo.BankTest#testTransfer(..)");
}

call("demo.Bank#transfer(..)") selects calls to the transfer method. The selected calls are filtered out except calls by the testTransfer method, which is specified by within("demo.BankTest#testTransfer(..)"). Thus, a log message is printed only before the transfer method is called within the body of the testTransfer method.

The advice body above prints the values of $3 and balance. $3 represents the 3rd argument to the transfer method called. balance is a local variable of the caller method testTransfer. Since an advice body is executed in the context of a caller-side method, it can access a local variable and a field available in the caller-side method, that is, the testTransfer method specified by within. The BankTest class woven with the @Glue class above is almost equivalent to the following normal Java program:

public class BankTest extends TestCase {
    public void testTransfer() {
        Account mine = new Account();
        Account yours = new Account();
        BigDecimal pay = new BigDecimal(20000);
        BigDecimal balance = new BigDecimal(10000);
        Bank bank = new Bank();
        System.out.println("transfer: " + pay + " " + balance);
        assertEquals(balance, bank.transfer(yours, mine, pay));
    }
}


Copyright (C) 2006 by Shigeru Chiba. All rights reserved.