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
@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 bymethodPattern
is called.Pointcut get(String fieldPattern)
When the value of a field specified byfieldPattern
is 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 byannotationPattern
is accessed.Pointcut when(String javaExpression)
WhilejavaExpression
istrue
. This corresponds to AspectJ'sif
pointcut designator.Pointcut cflow(String methodPattern)
While a method specified bymethodPattern
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.