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


[Next page]

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.

[Next page]