Weaving with AspectJ


AspectJ claims that it is a 'seamless aspect-oriented extension to the Java programming language that enables clean modularization of these crosscutting concerns'. I.e. it is a framework that allows to define aspects at particular manner and inject corresponding instructions directly to the byte code. Historically there was a dedicated aspect description language and extended java compiler that was able to understand it. AspectJ guys also introduced ability to define aspects via java5 annotations later. Feel free to get more information about AspectJ facilities and syntax at the AspectJ documentation page.

The main difference between Spring AOP and AspectJ AOP is that Spring AOP is proxy-based, i.e. it assumes that the client uses AOP-aware proxies instead of the 'raw' objects. AspectJ injects its instructions directly to the byte code.

Spring users can switch to AspectJ immediately in the case of Spring2 AOP usage - spring uses subset of AspectJ pointcut expression language, and @AspectJ spring aspects are fully eligible for AspectJ weaving.

Lets define a simple test-case that shows AspectJ weaving:


TestTarget.java
package com.aspectj;

public class TestTarget {

    public static void main(String[] args) {

        System.out.println("----------------------->--------- Start test -----------<---------------------");

        new TestTarget().test();

        System.out.println("----------------------->--------- End test -----------<---------------------");

    }

    public void test() {

        System.out.println("TestTarget.test()");

    }

}

TestAspect.java

package com.aspectj;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.JoinPoint;

@Aspect

public class TestAspect {

    @Before("execution (* com.aspectj.TestTarget.test*(..))")

    public void advice(JoinPoint joinPoint) {

        System.out.printf("TestAspect.advice() called on '%s'%n", joinPoint);

    }

}


aop.xml (keep it under Meta-inf folder for runtime loading)


    

        

    

    

        

    






What do we want is to see that aspect method is called when TestTarget.test() is invoked.

There are three ways to inject instructions implied by AspectJ aspects:

compile-time weaving - compile either target source or aspect classes via dedicated aspectj compiler;

post-compile weaving - inject aspect instructions to already compiled classes;

load-time weaving - inject aspect instructions to the byte code during class loading, i.e. load instrumented class instead of the 'raw' one;

It's possible to use any of the approaches mentioned above via various ways. I'm big fan of the law of leaky abstractions, so, lets perform weaving at the lowest level at first.

Lets define our directories structure for the example:



Here *.jar files are AspectJ binaries:

aspectjrt.jar - necessary in runtime for correct aspects processing;

aspectjtools.jar - contains implementation of aspectj compiler;

aspectjweaver.jar - bridge between aspectj logic and java instrumentation;


*.xml files are:

aop.xml - aspectj loadtime descriptor;

build.xml - ant script;

pom.xml - maven descriptor;


Command-line weaving


Compile-time and post-compile-time weaving is performed via ajc tool that stands for aspectj compiler. It allows to weave aspects at compile-time. Feel free to read more about it at its documentation.



Compile-time weaving

compile-time-weaving.sh
------------------------------
#!/bin/bash

# Prepare
echo "Preparing the environment..."
rm -rf ./target 2>/dev/null
CLASSES_DIR=./target/classes/compile-time
COUNTER=1
CURRENT_DIR=
while :
do
    DIR=`echo "$CLASSES_DIR" | cut -d'/' -f $COUNTER`
    test "$DIR" = "" && break
    CURRENT_DIR=${CURRENT_DIR}${DIR}/
    mkdir $CURRENT_DIR 2>/dev/null
    COUNTER=`expr $COUNTER + 1`
done

CLASSPATH=./src/main/java
for i in 'aspectjtools.jar' 'aspectjrt.jar'
do
    CLASSPATH=$CLASSPATH:./src/main/resources/$i
done

# Compile the sources
echo "Compiling..."
java -cp $CLASSPATH org.aspectj.tools.ajc.Main -source 1.5 -d $CLASSES_DIR src/main/java/com/aspectj/TestTarget.java src/main/java/com/aspectj/TestAspect.java

# Run the example and check that aspect logic is applied
echo "Running the sample..."
java -cp $CLASSPATH:$CLASSES_DIR com.aspectj.TestTarget
------------------------------------------------------------------------------------------------------------

It compiles target class and aspect class and runs target class. Following output is produced:

denis@harmony:/storage/projects/java/test$ ./compile-time-weaving.sh
Preparing the environment...
Compiling...
Running the sample...
----------------------->--------- Start test -----------<---------------------
TestAspect.advice() called on 'execution(void com.aspectj.TestTarget.test())'
TestTarget.test()
----------------------->--------- End test -----------<---------------------


We can see that aspect logic is introduced to the target class.

Note: it is assumed that java remains at the path.

Post-compile weaving

The general idea here is to inject aspect logic to the existing binaries. It's very useful when you work with third-party libraries. AspectJ keeps original byte code untouched and produces the new one with aspect logic inside it.

post-compile-weaving.sh script shows that approach:

#!/bin/bash

function ensure-dir-exists {
    COUNTER=1
    CURRENT_DIR=
    while :
    do
        DIR=`echo "$1" | cut -d'/' -f $COUNTER`
        test "$DIR" = "" && break
        CURRENT_DIR=${CURRENT_DIR}${DIR}/
        mkdir $CURRENT_DIR 2>/dev/null
        COUNTER=`expr $COUNTER + 1`
    done
}

# Prepare
echo "Preparing the environment..."
rm -rf ./target 2>/dev/null
JAR_DIR=./target/classes/post-compile-time
ensure-dir-exists $JAR_DIR

CLASSES_DIR=./target/classes/pure
ensure-dir-exists $CLASSES_DIR

CLASSPATH=./src/main/java
for i in 'aspectjtools.jar' 'aspectjrt.jar'
do
    CLASSPATH=$CLASSPATH:./src/main/resources/$i
done

# Compile the sources
echo "Compiling..."
javac -classpath $CLASSPATH -g -d $CLASSES_DIR src/main/java/com/aspectj/TestTarget.java src/main/java/com/aspectj/TestAspect.java

echo "Weaving aspect..."
java -cp $CLASSPATH org.aspectj.tools.ajc.Main -source 1.5 -inpath $CLASSES_DIR -aspectpath ./src/main/java -outjar $JAR_DIR/test.jar

# Run the example and check that aspect logic is applied
echo "Running the sample..."
java -cp $CLASSPATH:$JAR_DIR/test.jar com.aspectj.TestTarget
------------------------------------------------------------------------------------------------------------

This script compiles sources using standard javac compiler and weaves the aspects to the binary code. The output shows that aspect is correctly woven.

Load-time weaving

Aspects logic is injected to the class byte code during loading classes to the JVM. Standard java instrumentation facilities are used for that. More information about load-time weaving may be found here.

load-time-weaving.sh contains the following instructions:

#!/bin/bash

function ensure-dir-exists {
    COUNTER=1
    CURRENT_DIR=
    while :
    do
        DIR=`echo "$1" | cut -d'/' -f $COUNTER`
        test "$DIR" = "" && break
        CURRENT_DIR=${CURRENT_DIR}${DIR}/
        mkdir $CURRENT_DIR 2>/dev/null
        COUNTER=`expr $COUNTER + 1`
    done
}

# Prepare
echo "Preparing the environment..."
rm -rf ./target 2>/dev/null
CLASSES_DIR=./target/classes/pure
ensure-dir-exists $CLASSES_DIR

CLASSPATH=./src/main/java
for i in 'aspectjweaver.jar' 'aspectjrt.jar'
do
    CLASSPATH=$CLASSPATH:./src/main/resources/$i
done

# Compile the sources
echo "Compiling..."
javac -classpath $CLASSPATH -g -d $CLASSES_DIR src/main/java/com/aspectj/TestTarget.java src/main/java/com/aspectj/TestAspect.java

# Run the example and check that aspect logic is applied
echo "Running the sample..."
java -javaagent:./src/main/resources/aspectjweaver.jar -cp $CLASSPATH:$CLASSES_DIR:./src/main/resources com.aspectj.TestTarget

------------------------------------------------------------------------------------------------------------
If we run the example we get the following:

denis@harmony:/storage/projects/java/test$ java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03)
Java HotSpot(TM) Client VM (build 14.1-b02, mixed mode, sharing)
denis@harmony:/storage/projects/java/test$ ./load-time-weaving.sh
Preparing the environment...
Compiling...
Running the sample...
[AppClassLoader@17590db] info AspectJ Weaver Version 1.6.5 built on Thursday Jun 18, 2009 at 03:42:32 GMT
[AppClassLoader@17590db] info register classloader sun.misc.Launcher$AppClassLoader@17590db
[AppClassLoader@17590db] info using configuration /storage/projects/java/test/src/main/resources/META-INF/aop.xml
[AppClassLoader@17590db] info register aspect com.aspectj.TestAspect
----------------------->--------- Start test -----------<---------------------
TestAspect.advice() called on 'execution(void com.aspectj.TestTarget.test())'
TestTarget.test()
----------------------->--------- End test -----------<---------------------


Ant weaving

We know now how to weave by hand, lets consider using more convenient ways. The first one is a honorable ant:

build.xml

<project name="aspectj-example" xmlns:aspectj="antlib:org.aspectj">

    <property name="src.dir" value="src/main/java">

    <property name="resource.dir" value="src/main/resources">

    <property name="target.dir" value="target">

    <property name="classes.dir" value="${target.dir}/classes">

    <taskdef classpath="${resource.dir}/aspectjtools.jar" resource="org/aspectj/antlib.xml" uri="antlib:org.aspectj">

    <path id="aspectj.libs">

        <fileset dir="${resource.dir}">

    </fileset></path>

    <target name="clean">

        <delete dir="${target.dir}">

        <mkdir dir="${target.dir}">

        <mkdir dir="${classes.dir}">

    </mkdir></mkdir></delete></target>

    <target depends="clean" name="compiletime">

        <aspectj:iajc classpathref="aspectj.libs" destdir="${classes.dir}" source="1.5" srcdir="${src.dir}">

        <java classname="com.aspectj.TestTarget" fork="true">

            <classpath>

                <path refid="aspectj.libs">

                <pathelement path="${classes.dir}">

            </pathelement></path></classpath>

        </java>

    </aspectj:iajc></target>

    <target depends="clean" name="postcompile">

        <echo message="Compiling...">

        <javac classpathref="aspectj.libs" debug="true" destdir="${classes.dir}" srcdir="${src.dir}">

        <echo message="Weaving...">

        <aspectj:iajc aspectpath="${src.dir}" classpathref="aspectj.libs" inpath="${classes.dir}" outjar="${classes.dir}/test.jar">

        <echo message="Running...">

        <java classname="com.aspectj.TestTarget" fork="true">

            <classpath>

                <path refid="aspectj.libs">

                <pathelement path="${classes.dir}/test.jar">

            </pathelement></path></classpath>

        </java>

    </echo></aspectj:iajc></echo></javac></echo></target>

    <target depends="clean" name="loadtime">

        <echo message="Compiling...">

        <javac classpathref="aspectj.libs" debug="true" destdir="${classes.dir}" srcdir="${src.dir}">

        <echo message="Running...">

        <java classname="com.aspectj.TestTarget" fork="true">

            <jvmarg value="-javaagent:${resource.dir}/aspectjweaver.jar">

            <classpath>

                <path refid="aspectj.libs">

                <pathelement path="${classes.dir}">

                <pathelement path="${resource.dir}">

            </pathelement></pathelement></path></classpath>

        </jvmarg></java>

    </echo></javac></echo></target>

</taskdef></property></property></property></property></project>

------------------------------------------------------------------------------------------------------------
We can run 'ant compiletime', 'ant postcompile' and 'ant loadtime' and check that output is pretty much the same as the one from command-line scenarios.

Maven weaving


Finally let's consider the most convenient build tool - maven. There is a dedicated aspectj plugin that relieves the job.

pom.xml
----------
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelversion>4.0.0</modelversion>

    <groupid>com.test.aspectj</groupid>

    <artifactid>test</artifactid>

    <packaging>jar</packaging>

    <version>1.0-SNAPSHOT</version>

    <name>test</name>

    <url>http://maven.apache.org</url>

    <dependencies>

        <dependency>

            <groupid>org.aspectj</groupid>

            <artifactid>aspectjrt</artifactid>

            <version>1.6.5</version>

        </dependency>

    </dependencies>

    <build>

        <plugins>

            <plugin>

                <groupid>org.codehaus.mojo</groupid>

                <artifactid>aspectj-maven-plugin</artifactid>

                <version>1.2</version>

                <configuration>

                    <source></source>1.5

                    <target>1.5</target>

                </configuration>

                <executions>

                    <execution>

                        <goals>

                            <goal>compile</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

            <plugin>

                <groupid>org.codehaus.mojo</groupid>

                <artifactid>exec-maven-plugin</artifactid>

                <version>1.1</version>

                <executions>

                    <execution>

                        <phase>package</phase>

                        <goals>

                            <goal>java</goal>

                        </goals>

                    </execution>

                </executions>

                <configuration>

                    <mainclass>com.aspectj.TestTarget</mainclass>

                </configuration>

            </plugin>
     
        </plugins>

    </build>

</project>

</pre>

------------------------------------------------------------------------------------------------------------

If we run 'mvn install' we get the following output:

denis@harmony:/storage/projects/java/test$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building test
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [aspectj:compile {execution: default}]
[INFO] [resources:resources {execution: default-resources}]
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 4 resources
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /storage/projects/java/test/src/test/resources
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] No sources to compile
[INFO] [surefire:test {execution: default-test}]
[INFO] No tests to run.
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: /storage/projects/java/test/target/test-1.0-SNAPSHOT.jar
[INFO] Preparing exec:java
[WARNING] Removing: java from forked lifecycle, to prevent recursive invocation.
[INFO] No goals needed for project - skipping
[INFO] [exec:java {execution: default}]
----------------------->--------- Start test -----------<---------------------
TestAspect.advice() called on 'execution(void com.aspectj.TestTarget.test())'
TestTarget.test()
----------------------->--------- End test -----------<---------------------
[INFO] [install:install {execution: default-install}]
[INFO] Installing /storage/projects/java/test/target/test-1.0-SNAPSHOT.jar to /storage/maven-repository/com/test/aspectj/test/1.0-SNAPSHOT/test-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11 seconds
[INFO] Finished at: Sat Aug 15 09:58:30 MSD 2009
[INFO] Final Memory: 12M/21M
[INFO] ------------------------------------------------------------------------


It's also easy to weave dependencies to the existing jars that are used as a project dependencies - Weaving already compiled jar artifacts

project can be found @ http://dl.getdropbox.com/u/1648086/example/aspectj/weaving-example/aspectj-example.tar.gz

this content is copied from http://denis-zhdanov.blogspot.in/2009/08/weaving-with-aspectj.html

No comments:

Post a Comment