February 20, 2006

A Practical Guide to Using an Aspect Library (part I)

This entry represents part one of a two-part guide to using an aspect library (with AspectJ). I wrote it in December of last year, and have been waiting to finish part II before publishing it. But I finally realised with everything else I've got on at the moment it's probably best just to make this part available anyway! So with no further ado:

Introduction

With the arrival of AspectJ 5 and the Spring 2.0 milestone builds,
many of you will be working with AspectJ-based aspect libraries for
the first time. This article is a practical guide to get you up and
running as quickly as possible and with the least amount of
hassle. I'll cover development and unit testing, integration testing
and deployment, continuous integration, and production builds.

I'm assuming that for the time being you don't want to write your own
aspects: you just want to use the capabilities of an existing
library. As a running example, I'm going to use the aspect library
that ships with Spring 2.0: spring-aspects.jar. This library contains
an aspect that supports dependency injection of domain objects,
following the principles I outlined in the developerWorks
article "Dependency Injection with AspectJ and Spring"
(http://www-128.ibm.com/developerworks/java/library/j-aopwork13.html).

The library supports an @Configurable annotation. When an instance of
an @Configurable type is created, however it is created, it will be
dependency injected by Spring. The following examples demonstrate some
typical uses of the annotation:

@Configurable
public class Account {...}

When the annotation is used like this without a value, an instance of Account will be dependency injected by Spring, using the fully-qualifed class name of Account as the bean name.

@Configurable("accountBean")
public class Account {...}

When a value is supplied, it is used as the bean name: ;n instance of Account will be dependency injected by Spring, using "accountBean" as the bean name.

@Configurable(autowire=Autowire.BY_NAME)
public class Account {...}

An instance of Account will be dependency injected by Spring using
autowiring by name.

@Configurable(autowire=Autowire.BY_TYPE)
public class Account {...}

An instance of Account will be dependency injected by Spring using
autowiring by type.

When autowiring, it is also possible to require a dependency check by
specifying dependencyCheck="true". For example,

So let's get started developing an application that uses @Configurable...

Development, unit testing, and simple integration testing

Unit testing means testing the Account class in isolation. The standard benefits of dependency injection as related to enhanced testability apply here, and there is nothing special to do. In unit tests, we will manually pass in mock or stub implementations of the Account object's dependencies.

When it comes to simple integration testing (firing up a combination of objects in a Spring container and checking they behave as expected) we do want the dependency injection to happen (i.e. we do need the aspect in the library to be in effect).

You can annotate any type with @Configurable, but just using the
annotation on its own doesn't deliver the behaviour associated with
that annotation by the aspects in the spring-aspects library. You need
to link (or "weave") the aspects in the library with your application
for it to behave as desired at runtime. In this section I'm going to
focus on what you need to do to get your basic integration tests passing in your
IDE. I'll describe configuration for both Eclipse and IntelliJ
IDEA. If you use a different IDE it should be straightforward to setup
a similar environment.

First let's look at the sample project we'll be working with. The
project defines one domain object, "Account", a service interface
"AccountService", and a default implementation of that service,
"DefaultAccountService". There is an accompanying test
suite, "AccountTests". The Account class looks like this:

Note that "testAccountConfiguration" simply creates a new Account
object using the default constructor, and expects it to be dependency
injected. The test case configures a Spring application context from
"beans.xml". Here it is (using the new Spring 2.0 configuration style):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this element causes spring to configure the
AnnotationBeanConfigurer aspect in spring-aspects.jar
so that it has a reference to the bean factory
-->
<aop:spring-configured/>
<!-- configuration of Account bean, because we don't specify an
id, the class name will be used as the bean name. This
conveniently matches the behaviour of @Configurable when no
bean name is given in the annotation
-->
<bean
class="org.aspectprogrammer.samples.domain.Account"
lazy-init="true">
<property name="accountService" ref="accountService"/>
</bean>
<!-- service used by the Account bean -->
<bean id="accountService"
class="org.aspectprogrammer.samples.services.impl.DefaultAccountService">
</bean>
</beans>

You'll need spring-aspects.jar on the classpath for your project.There was a packaging issue with
the jar in Spring 2.0 M1, but if you're using M2 or higher everything should work as described in this article. You'll also need the AspectJ runtime
library, aspectjrt.jar on the classpath.

If we run the test case under JUnit right now it will fail with the
message "account service should have been provided". This is because
we haven't woven the application with the aspects in the library yet,
so there is no behaviour associated with the annotation. So how do we
make the tests pass?

Introducing Load-time Weaving

AspectJ is first and foremost a programming language. It supports
constructs called "aspects" in addition to the regular Java types. So
one way to use AspectJ is to write a program in the AspectJ language,
compile it with the AspectJ compiler (producing standard .class
files), and run it. AspectJ also lets you take pre-compiled aspects
(in a jar file for example) and link (weave) them with your
application classes. This can be done as an additional stage in a
build process (see later) or at runtime as classes are loaded into the
virtual machine. Weaving classes at loadtime goes by the name of
"Load-time weaving" or "LTW" for short.

Load-time weaving is a very easy and flexible way to use an existing
aspect library during development. The simplest way to use it is under
Java 5 using the AspectJ weaving Java agent.

Using LTW with JUnit in Eclipse and IDEA

To set this up in Eclipse, do the following:

Click on the Run... drop-down icon in the toolbar to open the run
configurations dialog. Click on "JUnit" and then press "New". Give the
configuration a name (I've called it "tests") and select the tests you
want to run (I've chosen to run all the tests in the project):

Click on the "Arguments" tab and in the "VM arguments" section enter:

-javaagent:lib/aspectjweaver.jar

The part after the ":" should be the path to your copy of
aspectjweaver.jar. In this case, I've copied the aspectjweaver.jar
from the Spring distribution into the lib directory of my project (it
doesnt' need to be on the project's classpath). You can use the jar
from the AspectJ 5 final release too if you want to.

That's it!

Save and run the configuration - you should see a green bar.

To set this up in IDEA:

Configuration in IDEA is very similar. Open the "Run/Debug
Configurations" dialog using the drop-down in the toolbar. Click the
"+" icon to create a new configuration and name it e.g. "tests".

For this project, I've selected "All in package" and search for tests
"In whole project".

Now all you have to do is add the VM startup parameter that brings in
the AspectJ LTW agent:

-javaagent:lib/aspectjweaver.jar

The part after the ":" should be the path to your copy of
aspectjweaver.jar. In this case, I've copied the aspectjweaver.jar
from the Spring distribution into the lib directory of my project (it
doesnt' need to be on the project's classpath). You can use the jar
from AspectJ 5 final release too if you want to.

That's it!

Save and run the configuration - you should see a green bar.

Running a Java application with LTW

To run a Java application (class with a "main" method) from your IDE
you can follow exactly the same process and set the javaagent VM
parameter. In Eclipse if you have the AJDT plugin installed, there is
also a dedicated launch type that gives you a few more options here.

In the run configurations dialog, create a new "AspectJ Load-Time
Weaving Application" launch configuration.

You'll see a new tab appear, "LTW Aspectpath". By selecting that tab
you can add aspect library jars from your project or from an external
source, and even aspects that are built by another project in your
workspace.

This can be a very convienent way of launching an application with a
diagnostics or instrumentation aspect library for example. Remember
that this all works with standard Java projects - you do not need to
convert your project to an AspectJ project.

Getting more information

So how did that work?

AspectJ's load-time weaving agent is configured by the use of aop.xml
files. It looks for one or more aop.xml files on the classpath in the
location "META-INF/aop.xml" and aggregates the contents to determine
the weaver configuration. If you look inside spring-aspects.jar,
you'll find a META-INF/aop.xml file in there that defines the Spring
aspects in the library to the weaver. (For full details on the aop.xml
file format and load-time weaving in general, see the AspectJ
Developer's Guide).

Because aop.xml files are aggregated we can define our own project
file and use it to give additional instructions to the weaver. One
useful option to pass is "-showWeaveInfo" which causes the weaving
agent to tell us exactly what it is doing.

Create a META-INF folder under the source root of your project
(e.g. "src/META-INF"). In that folder, create a simple aop.xml file as follows:

<aspectj>
<weaver options="-showWeaveInfo"/>
</aspectj>

When you run the tests again, take a closer look at the console
output. Here's the edited output of running the tests on my machine
(I've stripped the package names out for clarity):

Notice the weaveinfo message? AspectJ will tell you exactly what it is
doing - which join points in which types are advised by which
aspects. In this case, the "initialization" join point for the default
constructor of Account has been advised by "afterReturning" advice
from the "AnnotationBeanConfigurer" aspect (the advice is defined in
the super-aspect AbstractBeanConfigurer, at L96 in the source file).

If you want the AspectJ messages nicely integrated into the log output
(especially useful when it comes to deploying LTW in servers) you can
supply your own message handler class using the
"-XmessageHandlerClass" option. Spring supplies a message handler
class that integrates the AspectJ messages into commons logging output
produced by Spring itself. To use it add

Note that we're now also getting messages about the all of the aspects
in use and all of the types that the weaver is looking at (the
"weaving ..." messages). Of these, it is only the Account class in our
project that has the @Configurable annotation, and hence it is only
during the weaving of that type that we see a join point woven
message.

Being able to get detailed information about exactly what the weaver
is doing can be a useful aid in performance tuning and troubleshooting.

Controlling LTW with aop.xml

Controlling the weaving scope:

One of the things you might have noticed if you tried out the custom
message handling class is that the weaver looked at a large number of
types. This includes for example "junit.framework.TestSuite" and
several other types in the junit package. Load-Time Weaving will be
much more efficient if you scope it to just the types in your
application that you actually want to weave. In the case of our sample
application, that's the classes in the org.aspectprogrammer.samples
package and sub-packages. The weaving scope is controlled by the
aop.xml file. We can add one or more include and exclude elements to
control the set of types that the weaver will look at. If there are no
include statements, then the weaver will look at all types that are
not explicitly included. If there are one or more include statements,
then the weaver looks at all included but not excluded types. To weave
only the sample application classes we can update the aop.xml file as
follows:

The "within" attribute syntax is actually an AspectJ type pattern as
used in the AspectJ pointcut language. For now all you need to know is
that a pattern like "org.xyz.*" will include all types in the
"org.xyz" package, and a pattern like "org.xyz..*" will include all
types in the "org.xzy" package or any sub-package thereof.

Controlling which aspects are used:

The spring-aspects.jar contains several aspects. Since they are all defined by the
META-INF/aop.xml file in the jar, they will all be registered and used
for weaving. What if you want to use the @Configurable
(AnnotationBeanConfigurer) aspect, but not the @Transactional support
(AnnotationTransactionAspect)? You can control this via the aop.xml
file too. This time you need to use the <aspects> element:

<aspectj>
<!-- definitions of aspects available to the weaver, and which
ones should be used or not used -->
<aspects>
<exclude within="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
</aspects>
<!-- control over the weaver itself and the types that will be
woven -->
<weaver options="....">
<include within="org.aspectprogrammer.samples..*"/>
</weaver>

The include and exclude options in the <aspects> element work the same
way they do in the <weaver> element: if there are no include elements
specified then all defined aspects that are not explicitly excluded
are included. If there are one or more include elements, then all
included but not excluded aspects are used.

What if I can't use Java 5?

For development, of course you can use Java 5!

By which I mean that even if you are developing an application that
has to run under 1.3 or 1.4, you can still use a Java 5 VM with
-source and -target compiler flags set appropriately. The only
requirement to use load-time weaving in the way we have been
describing is that you run the tests on a 1.5 VM.

In part II, I'll be looking at integration testing and
deployment, and there I'll cover your options if you really can't use
Java 5 to run your test server.

(Note: as mentioned at the start of this article, part II will be forthcoming just as soon as I get some more writing cycles!)

Comments

Forcing to use Java 5 even for development is quite serious restriction because it makes it very easy to break 1.4 compatibility because developers can accidentally pickup API only available in Java 5.

Exemple: I started with a non-spring application, being hard to test because of dependencies being created with new. I refactored the application to use setters and made dependencies external in the spring config files... and now using this I can remove the setters and go back to using new operator. I guess that's not what i want.

Yes you're right, using the Java 5 runtime library on the classpath can still catch you out. I've been bitten that way myself when working on the AspectJ tree. Eclipse does let you change the runtime libraries you compile against (so you can avoid this error) but you have to remember to do it. Actually, I was overstrict in my statement because there is no need to develop in Java 5 at all. You can quite happily develop with 1.4 or 1.3, it's just that the JVM you run your tests under must be a 1.5 VM. In Eclipse at least, if you set the project to use, say, a 1.4 VM, then that VM will be used by default for any launch profiles you create for that project. Now you have to go in and change the VM used for the launch profile each time you create one. I find I create launch profiles more often than I change the project VM, so I find it marginally easier to configure a "1.5" project to build at "1.4" level (libraries and all).

Posted by: Adrian Colyer at February 21, 2006 12:28 PM

In response to Magnus...

This article was intended to be about the mechanisms of using an aspect library (any aspect library) rather than about the motivation for @Configurable itself. But since you ask.... in brief, the idea is not that you go back to instantiating service objects etc. yourself. @Configurable is targeted at domain objects (things like "Account") of which there will be many instances and which you do create yourself already (and your ORM tool, if you are using one, will also create for you). If such a domain object needs to get access to a service it can either do a lookup, or provide a setter operation and have someone provide it. @Configurable is aimed at that latter scenario, where the "someone" providing the service reference is the Spring container.

Posted by: Adrian Colyer at February 21, 2006 12:31 PM

It is possible to use AspectJ 5 load-time weaving with earlier Java VM versions. One configuration that's well worth considering at development time is to use a BEA JRockIt 1.4.x (or even 1.3.x) VM, which has an equivalent load-time weaving flag with builtin support in AspectJ 5. This is lower risk when working on a project that isn't using Java 5...

Alex Vasseur has also written a blog entry about using AspectJ load-time weaving with other pre-1.5 VM's: http://blogs.codehaus.org/people/avasseur/archives/%20001140_aspectj_5_load_time_weaving_with_java_13_using_aspectwerkz.html

Using the "AspectJ Load-Time Weaving Application" launch configuration in Eclipse does not require Java 5, just JDK 1.4. It uses an alternative mechanism that does not require the javaagent support in Java 5.

Posted by: Matthew Webster at March 3, 2006 02:25 PM

Mundane question, but how do you print this page? With IE it generates lots of blank pages. With firefox it only prints page 1, and page 2 is blank, and that's it.

Posted by: lumpynose at June 19, 2006 10:47 PM

I've updated the printing stylesheet - I now get to see all 10 pages when I print. Tested on firefox only, both Mac OS X and Windows.

Posted by: Adrian Colyer at June 22, 2006 01:53 PM

Are there any instructions available for making this work with build-time weaving and ADJT?

Great article....looking forward to part 2. Like Thomas, I'm hoping for instructions on build-time weaving. The Spring docs (section 6.8.1 of the spring 2.0 reference) say to use an ant or maven task, but does ADJT allow an AspectJ weaving step to be defined as a Builder in Eclipse?

Posted by: Travis M at August 24, 2006 06:39 PM

In section 6.8.1 of the spring 2.0 reference, the @Configurable example specifies a prototype bean: