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
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
@Glueis 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 bymethodPatternis called.Pointcut get(String fieldPattern)
When the value of a field specified byfieldPatternis obtained.Pointcut set(String fieldPattern)
When a new value is assigned to a field specified byfieldPattern.Pointcut within(String classPattern)
When a thread of control is within a method immediately declared in the class specified byclassPattern.Pointcut within(String methodPattern)
When a thread of control is within the body of a method specified bymethodPattern.Pointcut annotate(String annotationPattern)
When a method or a field with an annotation specified byannotationPatternis accessed.Pointcut when(String javaExpression)
WhilejavaExpressionistrue. This corresponds to AspectJ'sifpointcut designator.Pointcut cflow(String methodPattern)
While a method specified bymethodPatternis 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.