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 {
    protected 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 static class Account extends Account {
        protected 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 static class Afternoon extends Hello {
        public String header() {
            return "Good afternoon, ";
        }
    }
}

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

package test;

public class Hello {
    public 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 they are given a higher priority in that order. When they are woven, a child @Glue class with a higher priority is woven first. 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 existing 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 static class Diff extends test.Person {
        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. This static nested class is called a @Refine class. Only a static nested class can be a @Refine class; an inner class (i.e. non-static nested class) cannot be a @Refine class.

This nested @Refine class named Diff looks like a normal subclass of test.Person. However, since it is annotated by @Refine, it directly modifies the definition of Person. It appends a new field message to the Person class. Also, it substitutes the greet method declared in Diff for 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 extends keyword in the definition of the @Refine class. The name of a @Refine class has no special meaning. Other names such as Person2 and Expand are fine as well as Diff.

A subclass definition produces an extended class from its super class. The extended class and its original one coexist. On the other hand, a refinement produces an extended version of its super class (i.e. its target class) and furthermore substitutes it for the original super class. The extended class overrides the original one.

If the SayHello glue shown above is woven, the definition of Person is changed by the @Refine class to the following:

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

Although the SayHello glue 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 Diff refines;
}

public class Diff extends Person {
    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 extends. In the case above, the test.Person class is modified according to the Diff class. The name of the field does not have a special meaning. Any name can be given to it.

Add a new method

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

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

The following @Refine class appends the sayHi method to the test.Person class.

package test;

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

    public static class Test {
        public static void main(String[] args) {
            Person p = new Person();
            ((Diff)p).sayHi("Paul");
        }
    }
}

To call the sayHi method on a Person object, the type of the object must be cast from Person to Diff. See the main method in the test.Cheerful.Test class. The sayHi method will not be added until the glue is woven after compilation. At the source-code level, it is available only on the Diff type. After glues are woven, the name of a @Refine class is interpreted as being equivalent to the name of its target class.

A method in a @Refine class can call a method in its target (i.e. its super class).

package test;

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

        public void greet() {
            super.greet();
            System.out.println("How are you?");
        }
    }
}

super.greet() invokes the method originally declared in the Person class. On the other hand, greet() in the sayHi method invokes the greet method declared in the Diff class. This semantics is the same as that of normal method overriding and calls on super.

If there are more than one @Refine classes extending the same class, each of them refines the target class in turn in the order of the priority. A call on super reflects this semantics.

package test;

@Glue class Greeting1 {
    @Refine static class Hi extends Person {
        public void greet() {
            System.out.println("Hi!");
            super.greet();
        }
    }
}

@Glue class Greeting2 {
    @Refine static class Wow extends Person {
        public void greet() {
            System.out.println("Wow!");
            super.greet();
        }
    }
}

@Glue class Greeting {
    @Include Greeting1 glue1;
    @Include Greeting2 glue2;
}

If the Greeting is woven, the two @Refine classes Hi and Wow are applied in this order to extend the Person class. First, Hi extends the greet method in the Person class. Then, Wow extends the greet method that has been modified by Hi. Hence, super.greet() in Wow invokes the greet method that Hi has extended. The resulting behavior of the greet method is equivalent to the following one:

public void greet() {
    System.out.println("Wow!");
    System.out.println("Hi!");
    System.out.println("I'm " + name);
}

Override a static method

Unlike a regular class, a @Refine class can override a static method in its super class. For example,

package test;
public class Recursive {
    public static int factorial(int n) {
      if (n == 1)
          return 1;
      else
          return n * factrial(n - 1);
    }
}

The following @Refine class overrides the factorial method originally declared in the Recursive class:

package test;
@Glue class Iterative {
    @Refine static class Diff extends Recursive {
        public static int factorial(int n) {
            int f = 1;
            while (n > 1)
                f *= n--;
            return f;
        }
    }
}

If this @Glue class is woven, a call to Recursive.factorial computes the factorial of a given number by using the implementation described in the @Refine class Diff.

A static method in a @Refine class can call the method overridden by that static method.

package test;
@Glue class Terminator {
    @Refine static class Diff2 extends Recursive {
        public static int factorial(int n) {
            if (n >= 1)
                return Recursive.factorial(n);    // like a call on super.
            else
                return 0;
        }
    }
}

A call to the Recursive.factorial method invokes the original implementation of factorial. Recall that, unless the caller site is within the @Refine class Diff2, a call to the Recursive.factorial method invokes the implementation declared in the @Refine class instead of the original one. This semantics has been borrowed from method calls through super, which allows a method to call an overridden method declared in its super class.

Interfaces and fields

A @Refine class can append a field and an implemented interface to its target class.

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

The following @Refine class appends the three new members to the test.Person class. They are the who method, the Nameable interface, and the counter field.

package test;

interface Nameable {
    void who();
}

@Glue class WhoAreYou {
    @Refine static class Diff extends Person implements Nameable {
        public int counter = 0;
        public String name = "Anonym";
        public void who() {
            counter++;
            greet();
        }
    }
}

The test.Nameable interface is appended to the interfaces that the test.Person class implements. The who method included in the Nameable interface is also appended by this @Refine class.

The counter field is also appended to the Person class. The initial value of that field is 0. On the other hand, the name field is not appended because the Person class already includes that field. The @Refine class overrides the name field declared in the Person class. After the @Glue class above is woven, the initial value of the name field in Person is set to "Anonym". In the current implementation, the new initial value is assigned to the field just after the execution of a constructor of Person finishes. During the execution of the constructor, the field holds the original initial value. If the constructor calls another constructor by this(), the new initial value is assigned just after the call to another constructor finishes.

A @Refine class overridding a field in its super class can change the initial value of the field and also append annotations to the field. For example, the following @Refine class appends an annotation @Setter to the name field in the test.Person class:

package test;

public @inerface Setter {}

@Glue class Annotater {
    @Refine static class Diff extends Person {
        @Setter public String name;
    }
}

GluonJ allows a @Refine interface as well as a @Refine class. A @Refine interface can append a new method to its super interface. It can also append a new implemented interface to its super interface. For example, the following @Refine interface appends a new interface:

package test;

@Glue class Annotater {
    @Refine static interface Diff extends Nameable, Cloneable {
    }
}

This modifies the Nameable interface. The second super interface Cloneable is appended to the Nameable interface as its super interface. Note that the target of a @Refine interface is the first interface among the interfaces following extends. Thus, the second, third, ... interfaces are appended to the first one as its super interfaces.

Constructor of a @Refine class

Since a @Refine class is not a regular class, the definition of @Refine classes must follow the following restrictions:

  • No subclass of a @Refine class can be defined.

  • No instance of a @Refine class can be made.

  • The constructor of a @Refine class must be only the default constructor that takes no parameter.

If a target class does not have the default constructor that takes no parameter, the constructor of a @Refine class must call non-default constructor of the target class (i.e. its super class). However, this call is ignored when the @Refine class is woven. For example,

package test;

public class Counter {
    private int counter
    public Counter(int c) { counter = c; }
    public void decrement() {
        if (--counter <= 0)
            throw new RuntimeException("Bang!");
    }
}

@Glue class Increment {
    @Refine static Diff extends Counter {
        private int delta;
        public Diff() {
            super(0);
            delta = 1;
        }
        public void increment() {
            counter += delta;
        }
    }
}

when the @Refine class Diff is woven, a copy of the contents of the constructor of Diff is appended to the constructor of Counter. However, the call to the constructor of the super class is removed from the copy. For example, super(0) in the constructor of Diff is not copied. The extended constructor of Counter by the @Refien class is as the following:

public Counter(int c) {
    counter = c;
    delta = 1;          // copied from the @Refine class
}

Accessing private members

A @Refine class can access private methods and fields declared in its target class only if it is annotated by @Privileged. This feature is provided mainly for logging and tracing; you should avoid using it if possible because it violates the encapsulation property.

If a @Refine class declares a private field with the same name as a private field in the target class, that field is identical to the private field in the target class. Accesses to that field are regarded as accesses to the field in the target class.

package test;

public class Person {
    private String name;
    public void setName(String newName) { name = newName; }
    private String getName() { return name; }
    public String whoAreYou() { return "I'm " + getName(); }
}
package test;

@Glue class PersonExtender {
    @Refine @Privileged
    static class Diff extends Person {
        private String name;
        public String whoAreYou() { return "My name is " + name; }
    }
}

In the example above, the name field in the Diff class is identical to the name field in the Person class. Hence, the access to name in the whoAreYou method is regarded as an access to the private field name in the Person. For example, if the @Glue class above is woven,

Person p = new Person();
p.setName("Bill");
String s = p.whoAreYou();

The value of s is "My name is Bill". It is not "I'm Bill" or "My name is null".

A @Refine class can also override the initial value of a private field declared in its target class:

package test;

@Glue class PersonExtender2 {
    @Refine @Privileged
    static class Diff2 extends Person {
        private String name = "Unknown";
    }
}

This @Refine class changes the initial value of the name field declared in the Person class. The new value is "Unknown".

Overriding a private method is also allowed if a @Refine class is privileged:

package test;

@Glue class PersonExtender3 {
    @Refine @Privileged
    static class Diff3 extends Person {
        private String name;
        private String getName() {
            return name.toUpperCase();
        }
    }
}

If the Person class is woven with this @Glue class, the whoAreYou method on a Person object calls the getName method implemented in the Diff3 class.

Finally, a method in a @Refine class can call a private method in its target class if the @Refine is privileged.

package test;

@Glue class PersonExtender4 {
    @Refine @Privileged
    static abstract class Diff4 extends Person {
        abstract String super_getName();
        public String whoAreYou() {
            return "My name is " + super_getName();
        }
    }
}

To call a private method getName in Person, an abstract method named super_getName must be declared in the @Refine class. The parameter types of the super_getName must be the same as those of getName in the target class. The method name must start with super_, which is followed by the name of the private method in the target class. A call to the super_getName method is translated into a call to the getName method in the target class when the @Glue class is woven.

A method in a @Refine class can call a private method overridden by that method:

package test;

@Glue class PersonExtender5 {
    @Refine @Privileged
    static abstract class Diff5 extends Person {
        abstract String super_getName();
        private String getName() {
            return super_getName().toUpperCase();
        }
    }
}

Note that the getName method in the @Refine class overrides the private method getName in the target class Person. This overriding method getName calls the super_getName method declared in the same class. This call is translated into a call to the getName implemented in the target class Person.

Summary of @Refine

The semantics of the extension by a @Refine class is almost the same as the extension by a subclass. Exceptions are:

  • A static method can be overridden.

  • The initial value of a field can be overridden.

  • The annotations of a field can be appended.

  • The constructor of a @Refine class must be the default constructor.

  • A @Refine class annotated by @Privileged can access and override private members in its target class.

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.