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
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.
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."
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.
|
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:
get(),set(),
method call(), and so on) composed with a within()/withincode()
pointcut.
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.
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();
}
}
|
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.
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):
int
such as
when using java.lang.reflect.Modifier)Class<? extends
java.lang.annotation.Annotation> to match one (any
of the) method runtime visible annotation(s)jrockit.ext.weaving.ClassFilter
instance to match the declaring typejrockit.ext.weaving.StringFilter
instance to match the method namejrockit.ext.weaving.ClassFilter
instance to match the method return typejrockit.ext.weaving.UserDefinedFilter
instance to implement a finer matching logic
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:
Class<? extends
java.lang.annotation.Annotation> to match one (any)
of the class runtime visible annotation(s)Class to match the class typeboolean to express if subtypes
are to be matched
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);
}
}
);
|
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:
The bytecode is not modified. The regular compilation pipeline from bytecode to executable code is followed in the JVM internals. There is no need to parse the bytecode instruction and represent those in some intermediate structures to manipulate them in the instrumenting framework (AOP weaver or bytecode instrumentation-based product).
The weaver becomes ubiquitous. Even though you may want to register subscriptions at startup time, it is no longer a requirement. This greatly reduces the startup time of an application since there is no need at all to analyze bytecode instructions to look for join points to intercept. This also provides the opportunity to develop truly dynamic systems—dynamic meaning deploying and undeploying aspects at any point in time without any extra overhead or complexity.
Because bytecode instrumentation no longer occurs, there
are no problems associated with the object model's double bookkeeping issue.
The subscription API relies on the java.lang.reflect.*
model that already provides this information in a familiar way to the
Java developer.
Because the bytecode of the woven classes does not get
modified, there is no risk of having conflicts between two
different agents changing
the bytecode in two different ways that may be
incompatible—hiding
properties of the original program from each other. The registration
order of the subscription acts as the precedence rule.
Note that if a class were Serializable, no hidden
structures to support the runtime execution of the woven advice are
added to it, so regular
serialization will be fully supported. Usually bytecode
instrumentation techniques need to ensure that serialization is
preserved (for example, dealing with the serialVersionUID
field).
By using JVM-level method dispatching, all reflective
calls (method invocation or field get or set) can be matched as if they
were regular calls and all
registered actions will then get triggered. This occurs without any
extra cost or implementation-specific details and complexity.
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.
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.
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.