All entries for September 2007
September 03, 2007
Spring @Transactional handling with AspectJ
Save yourself and skip past this if you don’t know what Spring, AspectJ or transactions are :)
Your mileage may vary.
Here at Internet Towers we needed to have fine-grained control over where our Spring 2 application starts and ends database transactions. We use AOP to some extent already, but it just wraps around every handleRequest method on every edit-mode Controller. There’s built in support for annotations so that you can wrap a method in a transactional simply by annotating it with @Transactional and putting <tx:annotation-driven />
in the configuration file. Sounds pretty simple.
But this is achieved using Spring’s proxy objects (more on AOP proxies) wrapping around the original controller, which only work if the method to be transactional is public and called from outside (otherwise the object is calling itself and bypassing the proxy that’s wrapped around it). This isn't great because it means you can't even use it on a call to handleRequestInternal
. The solution is to use full-blown AspectJ weaving, which supports the same @Transactional annotation but can weave into any method and can be called from inside or out.
There are two ways to weave the transaction code in: load-time and compile-time. Load-time needs extra arguments when the VM is loaded which sounds like it would be a lot of hassle to remember to reconfigure every VM the application is deployed on. The easier we can drop the application file into a server’s deploy directory, the better. Compile time is nicer as it’s then built into the application, so I've gone ahead with that. The following is a rough guide to what's needed.
Firstly you need AspectJ, mainly the jar files that come with it, and also to make sure you have spring-aspects.jar that comes with Spring.
Sample application context:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop">
<aop:spring-configured>
<bean id="annotationTransactionAspect" factory-method="aspectOf"
class="org.springframework.transaction.aspectj.AnnotationTransactionAspect">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- the rest of your application here -->
</beans>
Super. That's all the configuration the application needs to understand the @Transactional annotation. Now to add the weaving into the compile process. We use Ant to build, and there's an Ant task to do this. With the right options it will weave into your compiled classes. After some looking around I found that it's enough to just specify Spring's spring-aspects.jar.
Sample build.xml:
<target name="compileAndWeave">
<path id="web-src.compile.class.path">
<!-- paths to any libraries needed for compiling, including AspectJ -->
<path refid="external.libs.path" />
</path>
<!-- compile your source as normal, into some directory -->
<javac srcdir="src"
destdir="build/classes-preweave"
classpathref="web-src.compile.class.path" /><!-- load up the "iajc" task -->
<taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"
classpath="path/to/aspectj/aspectjtools.jar"/>
<!-- weave the just compiled classes from classes-preweave into classes -->
<iajc
inpath="build/classes-preweave"
destdir="build/classes"
classpathref="web-src.compile.class.path"
aspectpath="path/to/spring/spring-aspects.jar"
verbose="true" />
</target>
That was a bit long, but the first half of it is just the standard compiling stuff.
Anyway, drop your shiny packaged war file into Tomcat or whatever and it should just work, handling transactions according to the "transactionManager" bean wherever you add the @Transactional annotation. If you annotate a class it will work as if you annotated all the public methods in that class.
Good luck!