AspectJ support for javax.transaction.Transactional

Issue: SPR-11803
This commit is contained in:
Stephane Nicoll 2015-02-12 14:45:47 +01:00
parent d75f390360
commit fa8d202a45
5 changed files with 270 additions and 3 deletions

View File

@ -42,6 +42,7 @@ configure(allprojects) { project ->
ext.jasperReportsVersion = "6.0.3"
ext.jettyVersion = "9.2.7.v20150116"
ext.jodaVersion = "2.6"
ext.jtaVersion = "1.2"
ext.junitVersion = "4.12"
ext.nettyVersion = "4.0.25.Final"
ext.openJpaVersion = "2.2.2" // 2.3.0 not Java 8 compatible (based on ASM 4)
@ -517,7 +518,7 @@ project("spring-tx") {
optional(project(":spring-aop"))
optional(project(":spring-context")) // for JCA, @EnableTransactionManagement
optional("aopalliance:aopalliance:1.0")
optional("javax.transaction:javax.transaction-api:1.2")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
optional("javax.resource:connector-api:1.5")
optional("javax.ejb:ejb-api:3.0")
optional("com.ibm.websphere:uow:6.0.2.17")
@ -577,7 +578,7 @@ project("spring-jms") {
provided("javax.jms:jms-api:1.1-rev-1")
optional(project(":spring-oxm"))
optional("aopalliance:aopalliance:1.0")
optional("javax.transaction:javax.transaction-api:1.2")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
optional("javax.resource:connector-api:1.5")
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
}
@ -591,7 +592,7 @@ project("spring-jdbc") {
compile(project(":spring-core"))
compile(project(":spring-tx"))
optional(project(":spring-context")) // for JndiDataSourceLookup
optional("javax.transaction:javax.transaction-api:1.2")
optional("javax.transaction:javax.transaction-api:${jtaVersion}")
optional("com.mchange:c3p0:0.9.2.1")
optional("org.hsqldb:hsqldb:${hsqldbVersion}")
optional("com.h2database:h2:1.4.182")
@ -1035,6 +1036,7 @@ project("spring-aspects") {
optional(project(":spring-context-support")) // for JavaMail and JSR-107 support
optional(project(":spring-orm")) // for JPA exception translation support
optional(project(":spring-tx")) // for JPA, @Transactional support
optional("javax.transaction:javax.transaction-api:${jtaVersion}") // for @javax.transaction.Transactional support
optional("javax.cache:cache-api:1.0.0")
testCompile(project(":spring-core")) // for CodeStyleAspect
testCompile(project(":spring-test"))

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.aspectj;
import javax.transaction.Transactional;
import org.aspectj.lang.annotation.RequiredTypes;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
/**
* Concrete AspectJ transaction aspect using {@code javax.transaction.Transactional} annotation.
*
* <p>When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited.
*
* <p>An @Transactional annotation on a class specifies the default transaction
* semantics for the execution of any <b>public</b> operation in the class.
*
* <p>An @Transactional annotation on a method within the class overrides the
* default transaction semantics given by the class annotation (if present).
* Any method may be annotated (regardless of visibility). Annotating
* non-public methods directly is the only way to get transaction demarcation
* for the execution of such operations.
*
* @author Stephane Nicoll
* @since 4.2
* @see Transactional
* @see AnnotationTransactionAspect
*/
@RequiredTypes({"javax.transaction.Transactional"})
public aspect JtaAnnotationTransactionAspect extends AbstractTransactionAspect {
public JtaAnnotationTransactionAspect() {
super(new AnnotationTransactionAttributeSource(false));
}
/**
* Matches the execution of any public method in a type with the Transactional
* annotation, or any subtype of a type with the Transactional annotation.
*/
private pointcut executionOfAnyPublicMethodInAtTransactionalType() :
execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *);
/**
* Matches the execution of any method with the Transactional annotation.
*/
private pointcut executionOfTransactionalMethod() :
execution(@Transactional * *(..));
/**
* Definition of pointcut from super aspect - matched join points
* will have Spring transaction management applied.
*/
protected pointcut transactionalMethodExecution(Object txObject) :
(executionOfAnyPublicMethodInAtTransactionalType()
|| executionOfTransactionalMethod() )
&& this(txObject);
}

View File

@ -13,6 +13,7 @@
<aspect name="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"/>
<aspect name="org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"/>
<aspect name="org.springframework.transaction.aspectj.AnnotationTransactionAspect"/>
<aspect name="org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect"/>
<aspect name="org.springframework.cache.aspectj.AnnotationCacheAspect"/>
<aspect name="org.springframework.cache.aspectj.JCacheCacheAspect"/>
</aspects>

View File

@ -0,0 +1,174 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.aspectj;
import java.io.IOException;
import javax.transaction.Transactional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.tests.transaction.CallCountingTransactionManager;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JtaTransactionAspectsTests.Config.class)
public class JtaTransactionAspectsTests {
@Autowired
private CallCountingTransactionManager txManager;
@Before
public void setUp() {
this.txManager.clear();
}
@Test
public void commitOnAnnotatedPublicMethod() throws Throwable {
assertEquals(0, this.txManager.begun);
new JtaAnnotationPublicAnnotatedMember().echo(null);
assertEquals(1, this.txManager.commits);
}
@Test
public void matchingRollbackOnApplied() throws Throwable {
assertEquals(0, this.txManager.begun);
InterruptedException test = new InterruptedException();
try {
new JtaAnnotationPublicAnnotatedMember().echo(test);
fail("Should have thrown an exception");
}
catch (Throwable throwable) {
assertEquals("wrong exception", test, throwable);
}
assertEquals(1, this.txManager.rollbacks);
assertEquals(0, this.txManager.commits);
}
@Test
public void nonMatchingRollbackOnApplied() throws Throwable {
assertEquals(0, this.txManager.begun);
IOException test = new IOException();
try {
new JtaAnnotationPublicAnnotatedMember().echo(test);
fail("Should have thrown an exception");
}
catch (Throwable throwable) {
assertEquals("wrong exception", test, throwable);
}
assertEquals(1, this.txManager.commits);
assertEquals(0, this.txManager.rollbacks);
}
@Test
public void commitOnAnnotatedProtectedMethod() {
assertEquals(0, this.txManager.begun);
new JtaAnnotationProtectedAnnotatedMember().doInTransaction();
assertEquals(1, this.txManager.commits);
}
@Test
public void nonAnnotatedMethodCallingProtectedMethod() {
assertEquals(0, this.txManager.begun);
new JtaAnnotationProtectedAnnotatedMember().doSomething();
assertEquals(1, this.txManager.commits);
}
@Test
public void commitOnAnnotatedPrivateMethod() {
assertEquals(0, this.txManager.begun);
new JtaAnnotationPrivateAnnotatedMember().doInTransaction();
assertEquals(1, this.txManager.commits);
}
@Test
public void nonAnnotatedMethodCallingPrivateMethod() {
assertEquals(0, this.txManager.begun);
new JtaAnnotationPrivateAnnotatedMember().doSomething();
assertEquals(1, this.txManager.commits);
}
@Test
public void notTransactional() {
assertEquals(0, this.txManager.begun);
new TransactionAspectTests.NotTransactional().noop();
assertEquals(0, this.txManager.begun);
}
public static class JtaAnnotationPublicAnnotatedMember {
@Transactional(rollbackOn = InterruptedException.class)
public void echo(Throwable t) throws Throwable {
if (t != null) {
throw t;
}
}
}
protected static class JtaAnnotationProtectedAnnotatedMember {
public void doSomething() {
doInTransaction();
}
@Transactional
protected void doInTransaction() {
}
}
protected static class JtaAnnotationPrivateAnnotatedMember {
public void doSomething() {
doInTransaction();
}
@Transactional
private void doInTransaction() {
}
}
@Configuration
protected static class Config {
@Bean
public CallCountingTransactionManager transactionManager() {
return new CallCountingTransactionManager();
}
@Bean
public JtaAnnotationTransactionAspect transactionAspect() {
JtaAnnotationTransactionAspect aspect = JtaAnnotationTransactionAspect.aspectOf();
aspect.setTransactionManager(transactionManager());
return aspect;
}
}
}

View File

@ -16060,6 +16060,13 @@ transaction semantics given by the class annotation (if present). Methods with `
default visibility methods directly is the only way to get transaction demarcation for
the execution of such methods.
[TIP]
====
Since Spring Framework 4.2, `spring-aspects` provides a similar aspect that offers the
exact same features for the standard `javax.transaction.Transactional` annotation. Check
`JtaAnnotationTransactionAspect` for more details.
====
For AspectJ programmers that want to use the Spring configuration and transaction
management support but don't want to (or cannot) use annotations, `spring-aspects.jar`
also contains `abstract` aspects you can extend to provide your own pointcut
@ -23762,6 +23769,13 @@ source code puts the declarations much closer to the affected code. There is not
danger of undue coupling, because code that is meant to be used transactionally is
almost always deployed that way anyway.
[NOTE]
====
The standard `javax.transaction.Transactional` annotation is also supported as a drop-in
replacement to Spring's own annotation. Please refer to JTA 1.2 documentation for more
details.
====
The ease-of-use afforded by the use of the `@Transactional` annotation is best
illustrated with an example, which is explained in the text that follows. Consider the
following class definition: