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
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 refinement
or a pointcut-advice. A @Glue
class can contain
any number of refinements and poitncut-advices.
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
but has better extensibility.
A pointcut-advice is a field of type Pointcut
declared in a @Glue
class. This field must be
annotated by @Before
, @After
,
or @Around
.
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
.
Load-time weaving on Apache Tomcat
You can run GluonJ's load-time weaver on Tomcat 5.x and 6.x.
To do that, you must first copy gluonj.jar
to
$CATALINA_HOME/lib
. Then you must set the environment
variable JAVA_OPS
in an initialization script
such as $CATALINA_HOME/bin/setenv.sh
(if your Tomcat is 6.x on Linux):
JAVA_OPTS="-javaagent:${CATALINA_HOME}/lib/gluonj.jar=javassist.gluonj.loader.tomcat.WeaverGlue -Djavassist.gluonj.classpath=${CATALINA_HOME}/lib/* ${JAVA _OPTS}"
This launch option requests the JVM to use GluonJ while running the Tomcat server.
Finally, you must specify a @Glue
class for each web application.
The @Glue
class is specified in the gluonj.properties
file
under WEB-INF/classes
. The contents of this file are just one line:
gluename=test.Logging
This specifies that test.Logging
is woven with the program of the
web application. If this file does not exist or it is empty (or it contains only comments
starting with #), then no @Glue
class will be woven at all.
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.
Copyright (C) 2006-2007 by Shigeru Chiba. All rights reserved.