Friday, January 05, 2007

Making transitive ASM dependencies work with Spring, Hibernate and AspectJ

I've recently had the opportunity to work with the new AspectJ integration in Spring 2.0, and it's been, for the most part, a pleasure. A very brief three-step tutorial could look something like this:

  1. Create a POJO that holds all your pointcuts, annotate the class with @Aspect and start defining your pointcuts:

    package se.petrix.stuff.aop;

    @Aspect
    public class MyPointcuts {

    @Before("execution (* * se.petrix.stuff.dao.*.*(..))")
    public void beforeAnyDaoMethod() {}

    }

    A pointcut answers the question of where the advice should be applied. In this case, the pointcut is defined in the AspectJ expression language and means "before the execution of any method, in any class in the package se.petrix.stuff.dao that takes any argument". It's the annotation that does all the work here, the actual method doesn't do anything, as you can see. It should however be namned in a self-explanatory way, since we will be referring to it later on.

  2. Write another class, which will become the actual advice. This class is also annotated with @Aspect:

    package se.petrix.stuff.aop;

    @Aspect
    public class DaoCallCounterAspect {

    private int callCount = 0;

    @Pointcut("se.petrix.stuff.aop.MyPointcuts.beforeAnyDaoMethod()")
    public void countDAOCall() {
    logger.info("DAO call count is now: " + (callCount++));
    }

    public int getDaoCallCount() { return callCount; }

    }

    This is now a before-advice, and refers to the previously created pointcut. All it does is count the number of calls the the DAO layer, which is yet another pointless example but at least it's not just logging. There's also a way to read the current call count.

  3. XML configuration!

    <bean class="se.petrix.stuff.aop.DaoCallCounterAspect"/>

    <aop:aspectj-autoproxy/>

    Not too bad, eh? You need the aop namespace configured of course. All weaving is automatic, based on the pointcut expressions.


It's completely straightforward the aspect class itself into another bean, for example you might want a controller to be able to read the DAO call count and display to a web page. And vice versa, you can wire other beans into the aspect.

It is of course possible to place pointcut definitions in the same class as the advice, and/or define pointcuts in XML and so on. This is just one way to organize it all. Read this chapter for more information, and take a look at the original AspectJ documentation.

Now to the main subject of this post: the transitive Maven jar dependency conflicts, or rather version mismatches, between Spring, Hibernate, CGLIB and ASM. We're using Spring 2.0 and Hibernate 3.2.0.ga (that's "general availability", not "Gavin Approved" contrary to popular belief), and AspectJ 1.5.3 (weaver and rt jars). This pulls in ASM 1.5.3 and CGLIB 2.1_3 through transitive dependencies defined in the Maven poms for those packages. But this will result in ASM classes not being found (something about node visitor, don't remember exactly), or if you're not careful when overriding dependencies, method signature mismatch (method not found). I don't have the details on which library requires which version, or the exact error messages, at the time of this writing, but I aim to provide a working solution (well, it works for us anyway). Maybe if I have the time, I'll do a more careful dependency analysis.

Anyway, here it goes: first of all, exclude the CGLIB dep from Hibernate, and replace it with the "cglib-nodep" version which includes the ASM files needed:

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.1_3</version>
</dependency

and

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.0.ga</version>
<exclusions>
<exclusion>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</exclusion>
</exclusions>
</dependency>

Then we specify ASM version 2.2.3 manually:

<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm-all</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm-attrs</artifactId>
<version>2.2.3</version>
</dependency>

The reason why we have an overlap with asm, asm-all and asm-attrs is that they are pulled in by another library (not sure which one), so we have to override the version to avoid ending up with different versions of these jars. Another way would probably be to exclude them and only have asm-all 2.2.3. As I said, I don't have the details, but we've struggled quite a bit to get this working and I thought it might useful to provide at working example, even without explaining exactly why it works :-).

No comments: