Zhu Wu's Blog

The world is a fine place and worth fighting for.

Use AspectJ to Modify Java Standard Library

This article explores the capabilities to alter Java standard library using AspectJ without recompiling source code of Java. It is highly experimental and do not apply it to production environment.

1. Weaving mode and development style

AspectJ provides three weaving modes by default: compile-time weaving, post-compile weaving and load-time weaving. As we do not want to modify the original Java source code, compile-time weaving is not applicable. Load-time weaving means the class is woven at the point that a class loader loads the class file and defines the class to the JVM. Since we are modifying JVM it self, load-time weaving is also not applicable. The only workable way is using post-compile weaving. Java standard library is defined in rt.jar, so we can write the logic in AspectJ, and use AspectJ compiler to weave compiled binary into rt.jar. The AspectJ code cannot be written in annotation style, because this style requires AspectJ library to be loaded in JVM, and it is not available when Java standard library is loaded.

The following command can be used to weave rt.jar into newrt.jar:

"$JAVA_HOME/bin/java" \
  -classpath "<path to aspectjtools.jar>:<path to aspectjrt.jar>:$JAVA_HOME/lib/tools.jar:$CLASSPATH" \
  -Xms256M -Xmx2048M org.aspectj.tools.ajc.Main \
  -8 -inpath $JAVA_HOME/jre/lib/rt.jar \
  [<AspectJ code path>...] \
  -outjar <path to newrt.jar>
To use the newrt.jar, we can pass the following parameter to java: -Xbootclasspath/p:<path to aspectjrt.jar>:<path to newrt.jar>.

2. Handle fields and methods defined in Java standard library

The code written in AspectJ is subject to the same access control rules as Java code by default. This can be restrictive if our AspectJ code needs to get value from private fields or call private methods. To overcome this, we can define aspects as priviledged, so all the private fields and methods are accessible.

It can be more complicated if we want to modify the value of a final field. This can be achieved by the following example. The code snippet aims to modify the value of private final field fd of FileOutputStream:

// Use reflection to get field fd.
final Field fdField = FileOutputStream.class.getDeclaredField("fd");

// Make field fd accessible. 
// However, this introduces a side effect that field fd is no longer private final.
Field.setAccessible0(fdField, true);

// Set a new value to field fd
fdField.set(this, new FileDescriptor());

3. Add new fields/methods/classes

Aspects can add fields, methods, and constructors to any existing class. For example, we can define a field called enabled, a method getEnabled(), and a new constructor to File:

private boolean File.enabled = false;
public boolean File.getEnabled() {
  return this.enabled;
}
public File.new(String s, boolean enabled) {
  // Constructor logic here.
}

The private above actually means private to the aspect, while public follows standard public behavior in Java.

To add a new class, we can define an aspect like the following, and add fields and methods in the same way as Java:

public aspect Foo {
  private int a;
  public Foo() {
    // Constructor details
  }
  public int getA() {
    return a;
  }
  public int setA(int a) {
    this.a = a;
  }
}

Then, aspect Foo can be used as a normal Java class.

4. Modify existing methods by AspectJ pointcuts and join points

The techniques are the same as normal AspectJ pointcuts and join points. You can refer to other introduction level blogs and AspectJ Cookbook for more details.