Jonas Bonér bio photo

Jonas Bonér

Present.
Entrepreneur.
Hacker.
Public Speaker.
Powder Skier.
Perpetual Learner.
Jazz Fanatic.
Wannabee Musician.

Twitter LinkedIn Github

One of the new features in the AspectWerkz 2 architecture is the ability to deploy and undeploy aspects at runtime, e.g. to do "hot" deployment and undeployment.

The implementation is based on HotSwap class redefinition and handles 'change sets' which ensures that the changes to each single join point is atomic. What this means in practice is that if you for example are deploying an aspect which has one before and one after advice, both advising the same join point (method/field etc.) then either both of them will be added or none.

The API exposed to the user is very simple and straight forward to use. Everything is handled by the Deployer class, which provides the following API for runtime deployment and undeployment of aspects:

DeploymentHandle deploy(Class aspect)
DeploymentHandle deploy(Class aspect, ClassLoader deployLoader)
DeploymentHandle deploy(Class aspect, DeploymentScope scope)
DeploymentHandle deploy(Class aspect, DeploymentScope scope, ClassLoader deployLoader)
DeploymentHandle deploy(Class aspect, String xmlDef)
DeploymentHandle deploy(Class aspect, String xmlDef, ClassLoader deployLoader)
DeploymentHandle deploy(Class aspect, String xmlDef, DeploymentScope scope)
DeploymentHandle deploy(Class aspect, String xmlDef, DeploymentScope scope, ClassLoader deployLoader)
void undeploy(Class aspect)
void undeploy(Class aspect, ClassLoader loader)
void undeploy(DeploymentHandle deploymentHandle)
As you can see you can for example choose to deploy an annotation defined aspect, e.g. a regular Java5 class with AspectWerkz defined annotations (or a regular Java 1.3/1.4 class with AspectWerkz defined JavaDoc annotations, compiled with AspectWerkz's JavaDoc annotation compiler):
  • in the context classloader (just specify the aspect class)
  • in an arbitrary class loader (this option is most useful for application server vendors that can garantuee that all the dependecies can be resolved)
  • within a specific DeploymentScope (more about this in a minute)
You can read more about how you can use the DeploymentHandle (that is returned from each of these methods) here.

You also deploy a class without any annotation definition and pass in the definition in XML format. Here you pass in a string containing the ... part of the regular aop.xml definition (see the online docs for details).

Use-case: Implementing a JMX monitoring aspect

Now let's take a look at a concrete example on how to hot deploy an aspect that can do some monitoring for us, report that to an JMX MBean and then undeploy it when the monitoring is done.

Writing the aspect

So first, here is the (main parts of the) JMX monitoring aspect:

@Aspect
public class ResponseTimeAspect {

    ... // methods for registering the MBean lazily, member fields etc.


    // the "invocationsToMonitor" pointcut will be defined at deployment time

    // in the aop.xml file

    @Around("invocationsToMonitor")
    public Object monitor(StaticJoinPoint jp) throws Throwable {
        Signature signature = jp.getSignature();
        long tsStart = System.currentTimeMillis();
        Object result = null;
        try {
            result = jp.proceed();
        } finally {
            // note: this code is using a JMX helper method and

            // we will have one MBean per jointpoint signature (i.e. method, field, etc.)

            long tsElapsed = System.currentTimeMillis() - tsStart;
            ObjectInstance mbeanI = registerMBean(signature);
            if (mbeanI != null) {
                m_mbeanServer.invoke(
                    mbeanI.getObjectName(),
                    "update",
                    new Object[]{new Long(tsElapsed)},
                    new String[]{long.class.getName()}
                );
            }
        }
        return result;
    }

    public static void enableMonitoring(String pointcut) { ... }

    public static void disableMonitoring() { ... }
}
As you can see, this aspect is defined using Java5 annotations, but if you are using Java 1.3/1.4 you can define the annnotations using JavaDoc (just put the annotation exactly as it is in the JavaDoc for the method/class) and run AnnotationC on the class. Then the rest in this article should stay the same. You can read more about the AnnotationC compiler here.

Hot deploy and undeploy the aspect

As you can see we have added two static methods to the aspect, these will be used to enable and disable the response time aspect. E.g. deploy it and undeploy it.

First we have the enableMonitoring method, which enables monitoring in our application, e.g. deploys the monitoring aspect. This method can be invoked at any point in time, all threading issues etc. are handled by the underlying implementation. Here we make use of the XML definition to resolve the annotation definition in the aspects by defining the "abstract pointcut" that we have bound the advice to (the invocationsToMonitor pointcut).

public static void enableMonitoring(String pointcut) {
    String xmlDef = "<aspect><pointcut name='invocationsToMonitor' expression='" + pointcut + "'/></aspect>";
    Deployer.deploy(ResponseTimeAspect.class, xmlDef);
}

Second we have the disableMonitoring method which disables monitoring for us, e.g. undeploys the aspect from all the join points where it had been defined (see the concept of DeploymentHandles for a more fine grained undeployment API).

public static void disableMonitoring() {
    Deployer.undeploy(ResponseTimeAspect.class);
}

Use the aspect to monitor JDBC SQL statements

This means that in the user code, all we need to do to enable and disabling this aspect is to invoke these two methods. Here we will monitor the executions of all JDBC statements:
public static void trackResponseTimeOfSqlQueries() {
    ResponseTimeAspect.enableMonitoring("call(* java.sql.Statement+.execute*(..))");

    ... // wait a while to do a recording


    ResponseTimeAspect.disableMonitoring();
}

What this pointcut expression (call(* java.sql.Statement+.execute*(..))) actually means is:

  • Pick out all method calls (call(..))
  • to alll methods that has a name that starts with execute (execute*)
  • that can have any parameter types ((..)) or return type (*)
  • that are in a concrete class that implements the java.sql.Statement interface (java.sql.Statement+)
By basing the pointcut expression on an interface we can be igonrant of how the actual JDBC driver is implemented (concrete subclasses etc.).

Note: we could in this specific use-case benefit from retrieving and storing the parameter value to the execute*(..) methods, since it is the actual SQL query that we are monitoring the execution time of. This could be done really easy but is out of scope for this article.

So now after doing some monitoring you should be able to easily see the data in any JMX console. For example hook in JConsole which you can read more about in this article.

Define a deployment scope

Finally, to get this to work in the general case we need to define a deployment scope which defines the set potential join points that we might want to monitor at runtime.

This construct gives you (as a application developer or vendor) control over which join points are exposed to the hot deployment API i.e. helps to prevent usage of the hot deployment facilities in specific areas of the application.

This can be done either in the META-INF/aop.xml file or using annotations in the aspect class. In this case we want to make the aspect generic so we will define it in the external XML definition:

<aspectwerkz>
    <system id="monitoring">
        <deployment-scope name="response_time" expression="call(* sample.deployment..*.*(..))"/>
    </system>
</aspectwerkz>

Here we have defined a deployment scope that picks out all method calls in our sample application, which means that we can safely deploy our ResponseTimeAspect at these points. (We could have added something like AND within(package..*) to the expression if we wanted to narrow down the scope.)

In the code above we have an implicit contract that says that we can not define the aspect to be deployed outside a deployment scope. If we do not define a pointcut that is wider than this it is all fine. But we can now use this deployment scope to ensure that we only deploy the aspect at valid points. To do that we need to retrieve a handle to the scope like this (can be done at any point):

    DeploymentScope scope = SystemDefinition.getDefinitionFor(loader, systemId).getDeploymentScope("response_time");

Now we can use the DeploymentScope handle to make a more explicit deployment (e.g. knowing exactly the points where our aspect will be applied):
Deployer.deploy(ResponseTimeAspect.class, xmlDef, scope);

Notes on the impact of the deployment scope preparation

The preparation made by the deployment scope construct means in practice that we are simply adding a level of indirection by adding a call to a public static final method that redirects to the target join point (method, field, constructor). This method will be inlined by all modern JVMs.

When we do undeploy of an aspect, if this aspect is the last one that affects the join point, then the class' bytecode will be reverted to the same state it had before any aspect was deployed.

Resources

More info about the JMX ResponseTimeAspect

The code for the monitoring aspect is based on the implementation of the ResponseTimeAspect in the AWare Project. The only difference is that in this example I am defining it using Java5 annotations and I have added the two static methods for doing the deployment and undeployment. You can read more about this aspect here and the source code for it can be found here.

You can also check out the complete AWare project which has some tests for the JMX module. Info about how to check out the sources can be found here.

More info about the Deployer Module

I have not written any specific sample application for this article but if you want you can look at and run the tests for the deployer module in the AspectWerkz distribution. You can download the distribution here. The tests are in ./src/jdk15/test directory and you can run them by invoking ant test:jdk15 when standing in the AspectWerkz distribution's root dir.

The aspectwerkz*.jar jars and the dependency jars are in the ./lib folder in the AspectWerkz distribution.

Enjoy.