JRockit JVM Support For AOP, Part 1

Pages: 1, 2, 3, 4

Samples of AOP in AspectJ

The following are simple code samples of AspectJ 5 that partially illustrate how the concepts defined above are mirrored in code. For further reference on specific AOP language details, refer to the AspectJ documentation.

// using a dedicated syntax
// that compliments the Java language
public aspect Foo {

  pointcut someListOperation() : call(* List+.add(..));

  pointcut userScope() : within(com.biz..*);

  before() : someListOperation() && userScope() {
    System.out.println("called: "
        + thisJoinPoint.getSignature()
    );
  }
}

The above code uses a dedicated syntax. You can write the equivalent code using Java annotations:

// the Java 5 annotations approach
@Aspect
public class Foo {

  @Pointcut("call(* java.util.List+.add(..))")
  public void someListOperation() {}

  @Pointcut("within(com.biz..*)")
  public void userScope() {}

  @Before("someListOperation() && userScope()")
  public void before(JoinPoint thisJoinPoint) {
    System.out.println("called: "
        + thisJoinPoint.getSignature()
    );
  }
}

The above code defines an aspect Foo with two pointcuts named someListOperation() and userScope(). These pointcuts will pick a set of join points in the application. They are composed together as a boolean expression someListOperation() && userScope() so that the before advice is executed before every call to any method named add on any instance of a type that extends List, providing that this call is done from within some code that stands in the com.biz package (and subpackages). The before advice will then print the signature of the method about to be called at any of those join points. The second code sample defines the very same aspect in an alternative syntax that relies on Java 5 annotations.

What is Weaving?

As described in the previous section and illustrated in the code samples, aspects can cross-cut the entire application. Weaving is the process of taking the aspects and the regular object-oriented application and "weaving" them into one single unit, one single application.

Weaving can happen at different periods in time:

  • Compile-time weaving: Post-processing the code, for example, ahead of deployment time (therefore ahead of runtime) (as in AspectJ 1.x).
  • Load-time weaving: Weaving is done as the classes are loaded—that is, at deployment time (as in AspectWerkz 2.0).
  • Runtime weaving: Weaving can occur at any time during the lifetime of the application (as in JRockit and the SteamLoom Project).

This process can also be done in many different ways:

  • Source-code weaving: Input is the developed source code, and output is modified source code that invokes the aspects (as in AspectJ 1.0).
  • Bytecode weaving: Input is the compiled application classes bytecode, and output is modified bytecode of the woven application (as in AspectWerkz 2.0 and AspectJ 1.1 and beyond).

Source-code weaving is limited in the sense that all source code must be available and presented to the weaver so that aspects can be applied. This makes it impossible, for example, to implement generic monitoring services. Compile-time weaving suffers from the same problem: All bytecode that will be deployed needs to be prepared before deployment in a post-compile-time phase.

This article series covers bytecode weaving vs. JVM weaving and will be discussed in the following sections.

As a side note, Dynamic proxies, a limited form of weaving, has been available in the JVM for some time. This API has been part of the JDK since 1.3, and it allows you to create a dynamic, virtual proxy of an interface (and/or a list of interfaces), which gives you the possibility of intercepting each invocation to the proxy and redirecting it anywhere you want. This is not really weaving by definition, but it resembles it in that it provides a simple way of doing method interception. It is used by various frameworks to do simple AOP, for example, the Spring Framework.

Problems with Bytecode Instrumentation-based Weaving

It is worth emphasizing that the problems described below are tied to bytecode instrumentation and, as a consequence, current AOP implementations such as AspectJ are suffering from it. These problems affect all bytecode instrumentation-based products in general, such as application monitoring solutions, profiling tools, or other AOP-applied solutions.

Instrumentation is inefficient

The actual instrumentation part of the weaving is usually very CPU-intensive, and sometimes also consumes a significant amount of memory. This can affect startup time. For example, to intercept all calls to the toString() method or all access to a certain field, you need to parse almost every single bytecode instruction in all classes, one by one. This also means that a lot of intermediate representation structures will be created by the bytecode instrumentation framework to expose the bytecode instructions in a usable way. This could potentially mean that the weaver needs to parse all bytecode instructions in all classes in the whole application (including third-party libraries and so on). In bad cases, this could amount to more than 10,000 classes.

If more than one weaver is used, the overhead will be multiplied.

Double bookkeeping: Building a class database for the weaver is expensive

To know whether or not a class, method, or field should be woven, the weaver needs to do matching on metadata for this class or member. Most AOP frameworks and AOP-applied products have some sort of high-level expression language ( pointcut expressions) to define where (at which join points) a code block (advice) should be woven in. These expression languages can, for example, let you pick out all methods that have a return type that implements an interface of type T. This information is not available in the bytecode instructions representing the call to a specific method M. The only way of knowing whether or not this specific method M should be woven is to look it up in some sort of class database, query its return type, and check if its return type implements the given interface T.

You may be thinking: Why not just use the java.lang.reflect.* API? The problem with using reflection here is that there's no way to query a Java type reflectively without triggering the class loading of this particular class, which will trigger the weaving of this class before we know enough about it to do the weaving (in load-time weaving infrastructures). Simply put: We end up with the classic chicken-and-egg problem.

The weaver therefore needs a class database (usually built up in memory from the raw bytecode read from the disk) to do the required queries on if needed for the actual join points . This problem can sometimes be avoided by limiting the expression language expressiveness, but that usually limits the usability of the product.

This in-memory class database is also redundant once the weaving is done. The JVM already has all this information in its own database, well optimized, (for example, it serves the java.lang.reflect API). So we end up doing double bookkeeping of the whole class structure (object model), which consumes significant and unnecessary memory, as well as adds a startup cost in creating this class database and maintaining it when a change occurs.

If more than one weaver is used, the overhead will be multiplied.

Pages: 1, 2, 3, 4

Next Page »