Synopsis of the original question: Using standard Spring Transactions with AOP proxying, it is not possible to call an @Transactional-marked method from a non-@Transactional-marked method in the same class and be within a transaction (specifically due to the aforementioned proxy). This is supposedly possible with Spring Transactions in AspectJ mode, but how is it done?
Edit: The full rundown for Spring Transactions in AspectJ mode using Load-Time Weaving:
Add the following to META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
<context:load-time-weaver />
(I'll assume you already have an AnnotationSessionFactoryBean
and a HibernateTransactionManager
set up in the application context. You can add transaction-manager="transactionManager"
as an attribute to your <tx:annotation-driven />
tag, but if the value of your transaction manager bean's id
attribute is actually "transactionManager
", then it's redundant, as "transactionManager
" is that attribute's default value.)
Add META-INF/aop.xml
. Contents are as follows:
<aspectj>
<aspects>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</aspects>
<weaver>
<include within="my.package..*" /><!--Whatever your package space is.-->
</weaver>
</aspectj>
Add aspectjweaver-1.7.0.jar
and spring-aspects-3.1.2.RELEASE.jar
to your classpath
. I use Maven as my build tool, so here are the <dependency />
declarations for your project's POM.xml
file:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
spring-instrument-3.1.2.RELEASE.jar
is not needed as a <dependency />
on your classpath
, but you still need it somewhere so that you can point at it with the -javaagent
JVM flag, as follows:
-javaagent:full\path\of\spring-instrument-3.1.2.RELEASE.jar
I'm working in Eclipse Juno, so to set this I went to Window -> Preferences -> Java -> Installed JREs. Then I clicked on the checked JRE in the list box and clicked the "Edit..." button to the right of the list box. The third text box in the resulting popup window is labeled "Default VM arguments:". This is where the -javaagent
flag should be typed or copy+pasted in.
Now for my actual test code classes. First, my main class, TestMain.java
:
package my.package;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
TestClass testClass = applicationContext.getBean(TestClass.class);
testClass.nonTransactionalMethod();
}
}
And then my transactional class, TestClass.java
:
package my.package;
import my.package.TestDao;
import my.package.TestObject;
import org.springframework.transaction.annotation.Transactional;
public void TestClass {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
this.testDao = testDao;
}
public TestDao getTestDao() {
return testDao;
}
public void nonTransactionalMethod() {
transactionalMethod();
}
@Transactional
private void transactionalMethod() {
TestObject testObject = new TestObject();
testObject.setId(1L);
testDao.save(testObject);
}
}
The trick here is that if the TestClass
is a field in TestMain
its class will be loaded by the ClassLoader
before the application context is loaded. Since the weaving is at the load-time of the class, and this weaving is done by Spring through the application context, it won't get woven because the class is already loaded before the application context is loaded and aware of it.
The further particulars of TestObject
and TestDao
are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />
's are set up in the application context file.
Edit: The full rundown for Spring Transactions in AspectJ mode using Compile-Time Weaving:
Add the following to META-INF/spring/applicationContext.xml
:
<tx:annotation-driven mode="aspectj" />
(I'll assume you already have an AnnotationSessionFactoryBean
and a HibernateTransactionManager
set up in the application context. You can add transaction-manager="transactionManager"
as an attribute to your <tx:annotation-driven />
tag, but if the value of your transaction manager bean's id
attribute is actually "transactionManager
", then it's redundant, as "transactionManager
" is that attribute's default value.)
Add spring-aspects-3.1.2.RELEASE.jar
and aspectjrt-1.7.0.jar
to your classpath
. I use Maven as my build tool, so here's the <dependency />
declarations for the POM.xml
file:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.0</version>
</dependency>
In Eclipse Juno: Help -> Eclipse Marketplace -> text box labeled "Find:" -> type "ajdt" -> hit [Enter] -> "AspectJ Development Tools (Juno)" -> Install -> Etc.
After restarting Eclipse (it will make you), right-click your project to bring up the context menu. Look near the bottom: Configure -> Convert to AspectJ Project.
Add the following <plugin />
declaration in your POM.xml
(again with the Maven!):
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
Alternative: Right-click your project to bring up the context menu. Look near the bottom: AspectJ Tools -> Configure AspectJ Build Path -> Aspect Path tab -> press "Add External JARs..." -> locate the full/path/of/spring-aspects-3.1.2.RELEASE.jar
-> press "Open" -> press "OK".
If you took the Maven route, the <plugin />
above should be freaking out. To fix this: Help -> Install New Software... -> press "Add..." -> type whatever you like in the text box labeled "Name:" -> type or copy+paste http://dist.springsource.org/release/AJDT/configurator/
in the text box labeled "Location:" -> press "OK" -> Wait a second -> check the parent checkbox next to "Maven Integration for Eclipse AJDT Integration" -> press "Next >" -> Install -> Etc.
When the plugin is installed, and you've restarted Eclipse, the errors in your POM.xml
file should have gone away. If not, right-click your project to bring up the context menu: Maven -> Update Project -> press "OK".
Now for my actual test code class. Only one this time, TestClass.java
:
package my.package;
import my.package.TestDao;
import my.package.TestObject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.annotation.Transactional;
public void TestClass {
private TestDao testDao;
public void setTestDao(TestDao testDao) {
this.testDao = testDao;
}
public TestDao getTestDao() {
return testDao;
}
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml");
TestClass testClass = applicationContext.getBean(TestClass.class);
testClass.nonTransactionalMethod();
}
public void nonTransactionalMethod() {
transactionalMethod();
}
@Transactional
private void transactionalMethod() {
TestObject testObject = new TestObject();
testObject.setId(1L);
testDao.save(testObject);
}
}
There is no trick to this one; since the weaving happens at compile time, which is before both the class loading and application context loading, the order of these two things no longer matters. This means that everything can go in the same class. In Eclipse, your code is constantly being re-compiled each time you hit Save (ever wondered what it was doing while it says "Building workspace: (XX%)"?), so it's woven and ready to go whenever you are.
Just like in the Load-Time example: the further particulars of TestObject
and TestDao
are unimportant. Assume they are wired up with JPA and Hibernate annotations and use Hibernate for persistence (because they are, and they do), and that all the requisite <bean />
's are set up in the application context file.
By reading your question it's not really clear where you are stuck, so I am going to briefly list what is needed to get AspectJ intercept your @Transactional
methods.
<tx:annotation-driven mode="aspectj"/>
in your Spring configuration file.<context:load-time-weaver/>
as well in your Spring configuration file.@Transactional
annotation: <aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
<include within="foo.*"/>
aspectjrt.jar
, aspectjweaver.jar
, spring-aspects.jar
and spring-aop.jar
in the classpath-javaagent:/path/to/spring-instrument.jar
(or spring-agent, as it is called in earlier releases)The final step may not be necessary. It is a really simple class that enables using the InstrumentationLoadTimeWeaver
, but if not available Spring will try to use another load time weaver. I have never tried that, though.
Now, if you think you have fulfilled all steps and still are having problems, I can recommend enabling some options on the weaver (defined in aop.xml):
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
This makes the weaver output a bunch of information what is being weaved. If you see classes being weaved, you can look for your TestClass
there. Then you at least have a starting point to continue troubleshooting.
Regarding your second edit, "It's almost like the weaving isn't happening fast enough to be woven before the class tries to execute.", the answer is yes, this can happen. I experienced a situation like this before.
I am a little rusty on the specifics, but basically it is something in the lines that Spring will not be able to weave classes that are loaded before the application context is being created. How are you creating your application context? If you are doing it programatically, and that class has a direct reference to TestClass
, then this problem could occur, since TestClass
will be loaded too early.
Unfortunately, I have found that debugging AspectJ is hell.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With