Introduction to GluonJ

Shigeru Chiba

Table of Contents

1. Why do you use GluonJ?
2. Writing a glue
3. Refinement (1)
4. Refinement (2)
5. Pointcut and Advice
6. Extending a glue


[Previous page]

5. 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. If the class name ends with +, subclasses of that class also match the pattern. 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. Thus, $0.getClass() returns the type of the target object. The name of the method is available from $name. 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));
    }
}

6. Extending a glue

You can define a subclass of @Glue class. When the subclass is woven, its super @Glue class is automatically woven together. The super class has the highest priority; first the super @Glue class is woven and then the @Glue classes included in the subclass are woven. Recall that the included @Glue classes are specified by @Include fields. The subclass is woven last. Note that each @Glue class is woven only once. If the super @Glue class is included again by an @Include field in the subclass, the weaver ignores the @Include field.

In a subclass of Glue class, a @Refine class can extend another @Refine class declared in the super @Glue class. For example, if you already have the following classes:

package test;

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

import javassist.gluonj.*;

@Glue class AbstractSayHello {
    @Refine static abstract class Diff extends Person {
        protected abstract String getMessage();
        public void greet() {
            System.out.print(getMessage());
            super.greet();
        }
    }
}

Then you can define the following subclass of AbstractSayHello:

package test;

import javassist.gluonj.*;

@Glue class SayHello extends AbstractSayHello {
    @Refine static class Diff2 extends Diff {
        protected String getMessage() {
            return "Hi, ";
        }
    }
}

The @Refine class Diff2 extends Diff, which is a @Refine class declared in the AbstractSayHello class. The target class of the @Refine class Diff2 is Person. If the super class of a @Refine class is also a @Refine class, the target classes of the subclass is inherited from its super class.

Note that the super @Refine class is applied to the target class before its subclass. Because the Diff class is applied to the Person class before Diff2, first the abstract method getMessage in Diff is appended to Person. Then, getMessage in Diff2 is appended. The resulting Person class declares the getMessage method returning "Hi, ".

If the super class of a @Refine class is also @Refine and declares a method annotated with @Super, then the subclass can call that method with @Super.

package test;

import javassist.gluonj.*;

public class Person {
    private String getName() { return "Steve"; }
    public void speak() {
        System.out.println("I'm " + getName());
    }
}
package test;

import javassist.gluonj.*;

@Glue class LastName {
    @Privileged @Refine static abstract class Diff extends Person {
        @Super("getName") abstract String super_getName();
        private String getName() { return super_getName() + " Jobs"; }
    }
}
package test;

@Glue class Mr extends LastName {
    @Privileged @Refine static abstract class Diff2 extends Diff {
        private String getName() { return "Mr. " + super_getName() }
    }
}

The getName method in Diff2 calls the super_getName method declared in its super class Diff. Since super_getName is annotated with @Super, it refers to the getName method in its target class.

Note that the body of the getName method that super_getName refers to is that of the getName method given by the Diff class. After weaving all the @Refine classes, the speak method in Person prints "I'm Mr. Steve Jobs". It is not "I'm Mr. Steve". A method annotated with @Super refers to the method in the target class but the body of the method referred to is the result of the refinement by the super @Refine classes. In the example above, when super_getName is called within the Diff2 class, it invokes the getName method in the Person class that results from the refinement by the super @Refine class Diff.


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

[Previous page]