Published on dev2dev (http://dev2dev.bea.com/)
 http://dev2dev.bea.com/pub/a/2005/08/jvm_aop_2.html
 See this if you're having trouble printing code examples

JRockit JVM Support For AOP, Part 2

by Alexandre Vasseur and Jonas Bonér, Joakim Dahlstedt
08/08/2005

Abstract

The previous article introduced you to aspect-oriented programming and the concept of separation of concerns to enhance software modularity with the help of the aspect construct, and how it is used as a complement to object-oriented programming. The aspect represents the unit of modularity, and is composed of pointcuts (the where), advice (the what), and inter-type declarations (that complement the object model in this new dimension). The concerns are woven into the application using different techniques, the most common, and widely adopted in today's Java landscape being bytecode instrumentation, as implemented in AspectWerkz and AspectJ (since 1.1).

Nevertheless, several shortcomings appear as consequences to this way of implementing AOP, as detailed in the first article of this series. Bytecode instrumentation is expensive despite a lot of ongoing work in this area (including the JVMTI/JSR-163 instrumentation agent specification in Java 5 and efficient bytecode manipulation libraries like ObjectWeb ASM). Using bytecode instrumentation to implement AOP weavers also proves to be incomplete, for example with reflective method calls or field get and set not matched by pointcuts without very specific and inefficient solutions. In general, all bytecode instrumentation-based products suffer from several problems tied to the bytecode instrumentation technique, and the problems will increase as this technique gains popularity.

All these shortcomings have led us—the JRockit team—to propose JVM support for AOP. The goal is to implement the widest set of current AOP semantics, while not locking the JVM with particular language details and programming model targeted by a specific aspect-oriented framework.

This article guides you through the proposed API, with concrete code samples, and then describes the benefits and discusses future directions.

Recap of Our Motivation

Let's quickly revise the technical motivation behind introducing JVM support for implementing AOP.

JVM weaving is the natural answer to most of the issues discussed above. To understand why, we will look at a couple examples that show that the JVM is already doing most of the work involved in weaving: When a class loads, the JVM reads the bytecode to build up the data needed to serve the java.lang.reflect.* API. Another example is method dispatching. Modern JVMs compile the bytecode of methods or code blocks to more advanced and efficient constructs and execution flows (doing code inlining where applicable). Due to the HotSwap API requirements, the JRockit JVM (and probably other JVMs too) also keeps track of which method calls which other method, so that a method body can still be hotswapped in all expected places—inlined or not—if its defining class is redefined at runtime.

As a consequence, instead of changing the bytecode to weave in an advice invocation—say, before a specific method call—the JVM could actually have knowledge about it and simply do a dispatch to the advice at any matching join point prior to dispatching to the actual method.

Because bytecode would be untouched, you can expect immediate advantages such as:

This is quite different from well-known C-level events that have been defined in the JVMDI specification such as JVMDI_EVENT_METHOD_ENTRY or JVMDI_EVENT_FIELD_ACCESS. In the JVMDI case, first one would have to deal with the C-level API, which makes it complex for most developers and fragile or complex to distribute; and second, the specification does not provide a fine-grained join point matching mechanism but actually requires you to subscribe to all such events. This still incurs an undeniable overhead, therefore the D(ebug) in "JVMDI."

Overview of Our Approach

We'd like to give you a taste of how we include JVM support for AOP. The key ingredient is that we expose action dispatch and subscription (described below) at the Java API level. You can then write code such as the following:

Weaver w = WeaverFactory.getWeaver();

Method staticActionMethod =  SimpleAction.class.getDeclaredMethod(
     "simpleStaticAction",
     new Class[0]//no arguments
);

MethodSubscription ms = new MethodSubscription(
   /* where to match*/,
   InsertionType.BEFORE,
   staticActionMethod
);
w.addSubscription(ms);

As you can see, we provide an accessible JVM API that can be used to implement the more traditional AOP approaches. This provides great flexibility in solving the traditional AOP implementation problems noted before, and opens the door for additional uses. The following sections examine this API in more detail.

Action Dispatch and Subscriptions

The JRockit JVM AOP support exposes a Java API that is deeply integrated in the JVM method dispatching and object model components. To make sure not to tie the JVM to any current or future AOP-specific technology direction, we have decided to implement an action dispatch and subscription model.

The API allows you to describe well-defined subscriptions at specified pointcuts, for which it is possible to register one action to which the JVM will dispatch. An action is composed of:

The action can be flagged as a before action, an after returning action, an after throwing action, or an instead-of action (similar to AOP around concept).

To invoke the API, you must get a handle to a jrockit.ext.weaving.Weaver instance. The weaver instance controls which operations are allowed according to its caller context. For example, you may not want a deployed application in an application server to create a weaver to subscribe action methods to some container-level or JDK-specific join points, while a container-level weaver may actually subscribe to application-specific join points. This weaver visibility concept mirrors the visibility rules from the underlying classloader's delegation model.

A simple comparison of how these constructs are mapped to the regular AOP constructs may shed some light on this model:

  • The subscription can be seen as a kinded pointcut, or actually a kinded pointcut (field get(),set(), method call(), and so on) composed with a within()/withincode() pointcut.
  • The action instance can be seen as the aspect instance.
  • The action method can be seen as the advice.
  • Readers familiar with AOP may already understand that to implement a complete AOP framework on top of this JVM-level API, some more development is required on an intermediate layer that will manage the aspect instantiation models (per clause), on implementing the cflow() pointcuts, and on implementing full pointcut composition and orthogonality.

    Details on the API: The action Method

    An action method (similar to the AOP advice concept) is like a regular Java method of a regular class (that acts as the aspect). It can either be a static method or a member method. Its return type has to follow some implicit conventions and should be void for a before action. It should also be of the type that will be placed on the stack as the result of the action invocation for an instead-of action (similar to AOP around advice semantics).

    The action method can have parameters, whose annotations further control context exposure, as illustrated in the following code sample:

    import java.lang.reflect.*;
    import jrockit.ext.weaving.*;
    
    public class SimpleAction {
    
      public static void simpleStaticAction() {
        out.println("hello static action!");
      }
    
      public void simpleAction() {
        out.println("hello action!");
      }
    
      public void simpleAction(
                    @CalleeMethod WMethod calleeM,
                    @CallerMethod WMethod callerM) {
        out.println(callerM.getMethod().getName());
        out.println(" calling ");
        out.println(calleeM.getMethod().getName());
      }
    
    }
    

    This code sample introduces the jrockit.ext.weaving.WMethod class. This class acts as a wrapper for java.lang.reflect.Method, java.lang.reflect.Constructor, and the class's static initializer, which is not represented in java.lang.reflect.*. This is similar to the AspectJ JoinPoint.StaticPart.getSignature() abstractions.

    Here is a list of the current set of defined annotations and their meaning:

    Annotation Exposes Notes
    @CalleeMethod The callee method (method, constructor, static initializer)  
    @CallerMethod The caller method (method, constructor, static initializer)  
    @Callee The callee instance Filters out static members invocation. Acts as an instance-of type filter: The callee type must be an instance of the annotated argument type.
    @Caller The caller instance Filters out invocations from within static members. Acts as an instance-of type filter: The caller type must be an instance of the annotated argument type.
    @Arguments The invocation arguments  

    To support instead-of and the ability to decide whether to proceed along the interception chain or not (as implemented in AOP through the concept of JoinPoint.proceed()), we introduced the jrockit.ext.weaving.InvocationContext construct as illustrated below.

    import jrockit.ext.weaving.*;
    
    public class InsteadOfAction {
    
      public Object instead(
                      InvocationContext jp,
                      @CalleeMethod Method callee) {
        return jp.proceed();
      }
    
    }
    

    Details on the API: The action Instance and the action Kind

    As illustrated in the previous code samples, an action method can be either static or not. If the action method is not static, you must pass in an action instance on which the JVM invokes the action method.

    This follows the syntax style with which a Java developer invokes a method reflectively using java.lang.reflect.Method.invoke(null/*static method*/, .../*args*/), although, unlike this example, with JVM AOP support, the underlying action invocation will not involve any reflection at all.

    Giving the user control over the action instance opens up interesting use cases. For example, you could implement a simple delegation pattern to swap a whole action instance with a different implementation at runtime without involving the JVM internals.

    Note that this will be useful to implement AOP aspect instantiation models (per clause) such as issingleton(), pertarget(), perthis(), percflow(), and so on, while not locking the JVM API to some predefined semantics.

    Before registering the subscription to the weaver instance, it is given a kind that acts as the advice kind: before, instead-of, after-returning, or after-throwing.

    To create a subscription, you can write something like the following:

    // Get a Weaver instance that will act as a
    // container for the subscription(s) we create
    Weaver w = WeaverFactory.getWeaver();
    
    // regular java.lang.reflect is used to refer
    // to the action method "simpleStaticAction()"
    Method staticActionMethod =
      SimpleAction.class.getDeclaredMethod(
          "simpleStaticAction",
          new Class[0]//no arguments
      );
    
    MethodSubscription ms = new MethodSubscription(
        .../* where to match*/,
        InsertionType.BEFORE,
        staticActionMethod
    );
    
    w.addSubscription(ms);
    
    

    This code sample assumes that you use a static action method implementation. Alternatively, you can code this using an instance method, in which case the MethodSubscription should be passed an instance of the containing class:

    // Use of an action instance to refer to the
    // non static action method "simpleAction()"
    Method actionMethod =
      SimpleAction.class.getDeclaredMethod(
          "simpleAction",
          new Class[0]// no arguments
      );
    // Instantiate the action instance
    SimpleAction actionInstance = new SimpleAction();
    MethodSubscription ms2 = new MethodSubscription(
        ...,// where to match, explained below
        InsertionType.BEFORE,
        actionMethod,
        actionInstance
    );
    
    w.addSubscription(ms2);
    

    AOP semantics such as within() and withincode() type patterns are also implemented through variations around this API.

    Details on the API: Subscriptions

    As shown in the previous code sample, the subscription API relies on the java.lang.reflect.* object model and some simple abstraction, such as jrockit.ext.weaving.WMethod, to unify Method, Constructor, and the class's static initializer handling.

    The first parameter of the new MethodSubscription(...) call must be a jrockit.ext.weaving.Filter instance, which has several concrete implementations to match on methods, fields, and so on.

    A jrockit.ext.weaving.MethodFilter instance acts as the definition on which the JVM weaver implementation does the join point shadow matching. The jrockit.ext.weaving.MethodFilter allows filtering on (and also exposes extra structures to support within()/withincode() semantics):

    The jrockit.ext.weaving.UserDefinedFilter callback mechanism is used to implement more advanced matching schemes (a concept similar to Spring AOP org.springframework.aop.MethodMatcher and org.springframework.aop.ClassFilter).

    All of these structures are optional, and if null is encountered, it means "match any."

    The jrockit.ext.weaving.ClassFilter exposes in a similar fashion:

    The following therefore matches all method calls whose name starts with "bar." Note that we pass in several null values in this very simple case:

    StringFilter sf =
      new StringFilter("bar", STARTSWITH);
    
    MethodFilter mf =
      new MethodFilter(0, null, null, sf, null, null);
    
    MethodSubscription ms = new MethodSubscription(
        mf,
        InsertionType.BEFORE,
        staticActionMethod
    );
    
    w.addSubscription(ms);
    

    As a more realistic topic, the following matches all three EJB business methods:

    // Prepare the pointcut to match
    // @Stateless annotated classes business methods
    MethodFilter ejbBizMethods = new MethodFilter(
        PUBLIC_NONSTATIC,
        // Method annotation does not matter
        null,
        new ClassFilter(
            // Declaring class, the java.lang.Class
            // for the EJB we are currently manipulating
            ejbClass,
            // no subtypes matching
            false,
            // class annotation
            Stateless.class
        ),
        // EJB methods matching is handled
        // in a UserDefinedFilter below instead
        null,
        // return type does not matter
        null,
        // custom Filter callback
        new UserDefinedFilter() {
            public boolean match(
                           MethodFilter methodFilter,
                           WMember member,
                           WMethod within) {
                return !isEjbLifeCycleMethod(member);
            }
        }
    );
    

    Benefits

    There are several benefits to using JVM weaving instead of bytecode instrumentation. From a high-level perspective, the weaving appears as a natural extension to the JVM's capabilities. It is less intrusive in many ways, with several benefits in the areas of performance, scalability, and usability.

    For a detailed discussion on the problems with bytecode weaving, in particular in a load-time weaving scenario, see Part 1 in this series. The following benefits solve all of these problems:

    Future directions

    Although JVM weaving brings huge value and addresses scalability and usability problems tied to bytecode instrumentation techniques, some interesting drawbacks must still be addressed to allow use cases to be completely addressed—possibly with complimentary approaches.

    Some bytecode instrumentation-based products are using fine-grained changes that may not be possible to mirror in the (current) JVM AOP API. Some use cases deal with the synchronized blocks, so that different locking strategies—for example, distributed—can be transparently injected into a regular application. Such a fine-grained manipulation usually requires conditional execution of the synchronized block, or even complete removal of it so that it is replaced by some proprietary locking API call. Such a specific need can be addressed within the JVM, but it is actually impossible to come up with a solution that works for each use case and that is efficient. It is also interesting to note that leading AOP frameworks are not (yet) exposing the synchronized block (or monitor entry and monitor exit lock acquisition and release) as join points.

    Some fine-grained semantics that AspectJ defines are not easily addressed at the JVM level. For example, AspectJ supports the concept of preinitialization, initialization, and constructor execution pointcuts. A constructor execution pointcut picks out the constructor as it appears in the source code, while the initialization pointcut picks out all constructor execution(s) that lead to having an initialized instance, including this(...) constructor delegations. This difference is not easily handled by the JVM. It may actually also be compiler dependant where more aggressive inlining strategies may occur.

    As bytecode instrumentation gains popularity, introducing a new JVM API is not without challenges. It would be fairly costly to develop a product that works for both JVMs that would support this API—such as JRockit—and for JVMs that do not support it. A specification (for example, a JSR) in this area would be beneficial.

    Conclusion

    Bytecode instrumentation techniques are now widely used in the Java platform in several different areas, ranging from aspect-oriented software development to more specific applied solutions such as application monitoring, persistence, or distributed computing. As they become more usable and transparent, the techniques of load-time weaving and instrumentation at deployment time are becoming popular.

    Unfortunately, such techniques do not provide the appropriate properties to match scalability and usability requirements, especially as it becomes more and more used, and mixed through the use of several different instrumenting agents from different products. JVM weaving and JVM support for AOP, as implemented in JRockit, happens to be a natural way to approach the problem to drive the innovation and the technology further. The proposed Java API that bridges JVM method dispatching internals to user-defined action and subscription depending solely on the java.lang.reflect API fills the gap elegantly and addresses major scalability and usability issues.

    Widespread adoption requires a good assessment of this new API, applied to real use cases—such as AOP or runtime adaptability of large applications.

    Additional Reading

    Jonas Bonér is a senior software engineer at the JRockit team at BEA Systems and is currently working on dynamic AOP, VM weaving and AOP evangelism. He is the founder of the AspectWerkz AOP framework and committer to the AspectJ 5 project.

    Joakim Dahlstedt is the CTO of the Java Runtime Products Group at BEA Systems, Inc., where he is responsible for the future development directions for the JVM.

    Alexandre Vasseur works as a software engineer at BEA Systems within the Java Runtime Product Group, focusing on Aspect Oriented Technology. He is the co-founder of the AspectWerkz AOP framework and committer on Eclipse AspectJ 5.


    Return to dev2dev.