« What's on your bookshelf? | Main | Tips for using Eclipse effectively »
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,
@Configurable(autowire=Autowire.BY_TYPE, dependencyCheck="true") public class Account {...}
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:
/** * Sample domain class using the Spring @Configurable annotation. */ @Configurable public class Account { private AccountService accountService; public void setAccountService(AccountService anAccountService) { this.accountService = anAccountService; } /** so that we can test it has been set appropriately */ public AccountService getAccountService() { return accountService; } }
It is expecting to be configured with an "AccountService" when it is instantiated. The test case verifies this is the case:
public class AccountTests extends AbstractSpringContextTests { public void testAccountConfiguration() { Account acc = new Account(); assertNotNull( "account service should have been provided", acc.getAccountService()); } protected void setUp() throws Exception { super.setUp(); getContext(new String[] { "org/aspectprogrammer/samples/domain/beans.xml" }); } }
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):
23-Dec-2005 13:45:08 AbstractSpringContextTests loadContextLocations INFO: Loading config for: org/aspectprogrammer/samples/domain/beans.xml 23-Dec-2005 13:45:08 XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [org/aspectprogrammer/samples/domain/beans.xml] weaveinfo Join point 'initialization(void Account.())' in Type 'org.aspectprogrammer.samples.domain.Account'(Account.java:15) advised by afterReturning advice from 'org.springframework.beans.factory.aspectj.AnnotationBeanConfigurer' (AbstractBeanConfigurer.aj:96) 23-Dec-2005 13:45:08 AbstractRefreshableApplicationContext refreshBeanFactory INFO: Bean factory for application context .....
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
-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler"
to the weaver options in your aop.xml file. This class isn't supplied in the Spring 2.0 M2 distribution, but is included in M3.
Here's an extract from a test run using the custom message handling class:
23-Dec-2005 14:34:58 AspectJWeaverMessageHandler handleMessage INFO: [AspectJ] register aspect org.springframework.beans.factory.aspectj.AnnotationBeanConfigurer ... INFO: [AspectJ] weaving 'org/aspectprogrammer/samples/domain/Account' 23-Dec-2005 14:34:58 AspectJWeaverMessageHandler handleMessage INFO: [AspectJ] Join point 'initialization(void domain.Account.())' in Type 'org.aspectprogrammer.samples.domain.Account' (Account.java:15) advised by afterReturning advice from 'org.springframework.beans.factory.aspectj.AnnotationBeanConfigurer' (AbstractBeanConfigurer.aj:96) 23-Dec-2005 14:34:58 AspectJWeaverMessageHandler handleMessage INFO: [AspectJ] weaving 'org/aspectprogrammer/samples/services/impl/DefaultAccountService' 23-Dec-2005 14:34:58 AspectJWeaverMessageHandler handleMessage INFO: [AspectJ] weaving 'org/aspectprogrammer/samples/services/AccountService' 23-Dec-2005 14:34:58 AbstractRefreshableApplicationContext refreshBeanFactory INFO: Bean factory for application context ...
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:
<aspectj> <weaver options="...."> <include within="org.aspectprogrammer.samples..*"/> </weaver> </aspectj>
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!)
Posted by adrian at February 20, 2006 01:40 PM [permalink]
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.
Posted by: Eugene Kuleshov at February 20, 2006 05:46 PM
What about best practices when and why to use this?
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.
Posted by: Magnus Heino at February 21, 2006 10:16 AM
In response to Eugene...
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
Posted by: Ron Bodkin at February 27, 2006 04:34 PM
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?
Posted by: Thomas Van de Velde at August 13, 2006 09:29 AM
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:
<bean class="com.xyz.myapp.domain.Account"
singleton="false">
<property name="fundsTransferService" ref="fundsTransferService"/>
...
</bean>
but your example above specifies a lazily-loaded singleton bean:
<bean
class="org.aspectprogrammer.samples.domain.Account"
lazy-init="true">
<property name="accountService" ref="accountService"/>
</bean>
Does it not matter whether the bean is a prototype or singleton?
Posted by: Travis M at August 25, 2006 05:40 AM
Nice blog with great tips ... Thanks.
Posted by: iqtf at September 10, 2009 12:22 AM
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)