From ae9e165d54c5a6cffa42f6ffcb13e72cad1774bb Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Fri, 6 May 2011 19:10:25 +0000 Subject: [PATCH] Introduce @EnableTransactionManagement git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4267 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../AbstractBeanFactoryPointcutAdvisor.java | 5 + ...ctJTransactionManagementConfiguration.java | 38 +++ ...rnateSessionFactoryConfigurationTests.java | 16 +- .../CallCountingTransactionManager.java | 59 ++++ ...TransactionManagementIntegrationTests.java | 306 ++++++++++++++++++ ...actTransactionManagementConfiguration.java | 64 ++++ .../EnableTransactionManagement.java | 58 ++++ ...oxyTransactionManagementConfiguration.java | 73 +++++ ...actionManagementConfigurationSelector.java | 45 +++ .../TransactionManagementConfigurer.java | 25 ++ .../AnnotationDrivenBeanDefinitionParser.java | 96 +++++- .../TransactionManagementConfigUtils.java | 39 +++ .../EnableTransactionManagementTests.java | 134 ++++++++ 13 files changed, 930 insertions(+), 28 deletions(-) create mode 100644 org.springframework.aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java create mode 100644 org.springframework.integration-tests/src/test/java/org/springframework/transaction/CallCountingTransactionManager.java create mode 100644 org.springframework.integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/config/TransactionManagementConfigUtils.java create mode 100644 org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java index 4a68cb2324c..0d11ab36a74 100644 --- a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java @@ -72,6 +72,11 @@ public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcu this.beanFactory = beanFactory; } + public void setAdvice(Advice advice) { + synchronized (this.adviceMonitor) { + this.advice = advice; + } + } public Advice getAdvice() { synchronized (this.adviceMonitor) { diff --git a/org.springframework.aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java b/org.springframework.aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java new file mode 100644 index 00000000000..1d250394c68 --- /dev/null +++ b/org.springframework.aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2011 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 org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.transaction.annotation.AbstractTransactionManagementConfiguration; +import org.springframework.transaction.config.TransactionManagementConfigUtils; + +@Configuration +public class AspectJTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + @Bean(name=TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public AnnotationTransactionAspect transactionAspect() { + AnnotationTransactionAspect txAspect = AnnotationTransactionAspect.aspectOf(); + if (this.txManager != null) { + txAspect.setTransactionManager(this.txManager); + } + return txAspect; + } +} diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java index 9444c3172bf..66344b54bee 100644 --- a/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java +++ b/org.springframework.integration-tests/src/test/java/org/springframework/orm/hibernate3/HibernateSessionFactoryConfigurationTests.java @@ -40,9 +40,6 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Feature; -import org.springframework.context.annotation.FeatureConfiguration; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; import org.springframework.dao.DataAccessException; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; @@ -53,8 +50,8 @@ import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBui import org.springframework.orm.hibernate3.scannable.Foo; import org.springframework.stereotype.Repository; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.config.TxAnnotationDriven; /** * Integration tests for configuring Hibernate SessionFactory types @@ -201,7 +198,7 @@ public class HibernateSessionFactoryConfigurationTests { @Configuration - @Import(TxConfig.class) + @EnableTransactionManagement static class RepositoryConfig { @Inject SessionFactory sessionFactory; @@ -227,15 +224,6 @@ public class HibernateSessionFactoryConfigurationTests { } - @FeatureConfiguration - static class TxConfig { - @Feature - TxAnnotationDriven tx(PlatformTransactionManager txManager) { - return new TxAnnotationDriven(txManager); - } - } - - @Configuration @ImportResource("org/springframework/orm/hibernate3/AnnotationSessionFactoryBeanXmlConfig-context.xml") static class AnnotationSessionFactoryBeanXmlConfig extends DataConfig { diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/transaction/CallCountingTransactionManager.java b/org.springframework.integration-tests/src/test/java/org/springframework/transaction/CallCountingTransactionManager.java new file mode 100644 index 00000000000..117d16d6a0c --- /dev/null +++ b/org.springframework.integration-tests/src/test/java/org/springframework/transaction/CallCountingTransactionManager.java @@ -0,0 +1,59 @@ +package org.springframework.transaction; +/* + * Copyright 2002-2007 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. + */ + + + +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class CallCountingTransactionManager extends AbstractPlatformTransactionManager { + + public TransactionDefinition lastDefinition; + public int begun; + public int commits; + public int rollbacks; + public int inflight; + + protected Object doGetTransaction() { + return new Object(); + } + + protected void doBegin(Object transaction, TransactionDefinition definition) { + this.lastDefinition = definition; + ++begun; + ++inflight; + } + + protected void doCommit(DefaultTransactionStatus status) { + ++commits; + --inflight; + } + + protected void doRollback(DefaultTransactionStatus status) { + ++rollbacks; + --inflight; + } + + public void clear() { + begun = commits = rollbacks = inflight = 0; + } + +} diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java b/org.springframework.integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java new file mode 100644 index 00000000000..230a48d3815 --- /dev/null +++ b/org.springframework.integration-tests/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementIntegrationTests.java @@ -0,0 +1,306 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collections; +import java.util.List; + +import javax.sql.DataSource; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.config.AdviceMode; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.CallCountingTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; + +/** + * Integration tests for the @EnableTransactionManagement annotation. + * + * @author Chris Beams + * @since 3.1 + */ +public class EnableTransactionManagementIntegrationTests { + + @Test + public void repositoryIsNotTxProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + + try { + assertTxProxying(ctx); + fail("expected exception"); + } catch (AssertionError ex) { + assertThat(ex.getMessage(), equalTo("FooRepository is not a TX proxy")); + } + } + + @Test + public void repositoryIsTxProxy_withDefaultTxManagerName() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, DefaultTxManagerNameConfig.class); + ctx.refresh(); + + assertTxProxying(ctx); + } + + @Test + public void repositoryIsTxProxy_withCustomTxManagerName() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, CustomTxManagerNameConfig.class); + ctx.refresh(); + + assertTxProxying(ctx); + } + + @Ignore @Test // TODO SPR-8207 + public void repositoryIsTxProxy_withNonConventionalTxManagerName_fallsBackToByTypeLookup() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, NonConventionalTxManagerNameConfig.class); + ctx.refresh(); + + assertTxProxying(ctx); + } + + @Test + public void repositoryIsClassBasedTxProxy() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, ProxyTargetClassTxConfig.class); + ctx.refresh(); + + assertTxProxying(ctx); + assertThat(AopUtils.isCglibProxy(ctx.getBean(FooRepository.class)), is(true)); + } + + @Test + public void repositoryUsesAspectJAdviceMode() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class, AspectJTxConfig.class); + try { + ctx.refresh(); + } catch (Exception ex) { + // this test is a bit fragile, but gets the job done, proving that an + // attempt was made to look up the AJ aspect. It's due to classpath issues + // in .integration-tests that it's not found. + assertTrue(ex.getMessage().endsWith("AspectJTransactionManagementConfiguration.class] cannot be opened because it does not exist")); + } + } + + @Test + public void implicitTxManager() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ImplicitTxManagerConfig.class); + ctx.refresh(); + + FooRepository fooRepository = ctx.getBean(FooRepository.class); + fooRepository.findAll(); + + CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class); + assertThat(txManager.begun, equalTo(1)); + assertThat(txManager.commits, equalTo(1)); + assertThat(txManager.rollbacks, equalTo(0)); + } + + @Test + public void explicitTxManager() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ExplicitTxManagerConfig.class); + ctx.refresh(); + + FooRepository fooRepository = ctx.getBean(FooRepository.class); + fooRepository.findAll(); + + CallCountingTransactionManager txManager1 = ctx.getBean("txManager1", CallCountingTransactionManager.class); + assertThat(txManager1.begun, equalTo(1)); + assertThat(txManager1.commits, equalTo(1)); + assertThat(txManager1.rollbacks, equalTo(0)); + + CallCountingTransactionManager txManager2 = ctx.getBean("txManager2", CallCountingTransactionManager.class); + assertThat(txManager2.begun, equalTo(0)); + assertThat(txManager2.commits, equalTo(0)); + assertThat(txManager2.rollbacks, equalTo(0)); + } + + + @Configuration + @EnableTransactionManagement + static class ImplicitTxManagerConfig { + @Bean + public PlatformTransactionManager txManager() { + return new CallCountingTransactionManager(); + } + + @Bean + public FooRepository fooRepository() { + return new DummyFooRepository(); + } + } + + + @Configuration + @EnableTransactionManagement + static class ExplicitTxManagerConfig implements TransactionManagementConfigurer { + @Bean + public PlatformTransactionManager txManager1() { + return new CallCountingTransactionManager(); + } + + @Bean + public PlatformTransactionManager txManager2() { + return new CallCountingTransactionManager(); + } + + public PlatformTransactionManager createTransactionManager() { + return txManager1(); + } + + @Bean + public FooRepository fooRepository() { + return new DummyFooRepository(); + } + } + + private void assertTxProxying(AnnotationConfigApplicationContext ctx) { + FooRepository repo = ctx.getBean(FooRepository.class); + + boolean isTxProxy = false; + if (AopUtils.isAopProxy(repo)) { + for (Advisor advisor : ((Advised)repo).getAdvisors()) { + if (advisor instanceof BeanFactoryTransactionAttributeSourceAdvisor) { + isTxProxy = true; + break; + } + } + } + assertTrue("FooRepository is not a TX proxy", isTxProxy); + + // trigger a transaction + repo.findAll(); + } + + + @Configuration + @EnableTransactionManagement + static class DefaultTxManagerNameConfig { + @Bean + PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + + @Configuration + @EnableTransactionManagement + static class CustomTxManagerNameConfig { + @Bean + PlatformTransactionManager txManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + + @Configuration + @EnableTransactionManagement + static class NonConventionalTxManagerNameConfig { + @Bean + PlatformTransactionManager txManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + + @Configuration + @EnableTransactionManagement(proxyTargetClass=true) + static class ProxyTargetClassTxConfig { + @Bean + PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + + @Configuration + @EnableTransactionManagement(mode=AdviceMode.ASPECTJ) + static class AspectJTxConfig { + @Bean + PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + + @Configuration + static class Config { + @Bean + FooRepository fooRepository() { + JdbcFooRepository repos = new JdbcFooRepository(); + repos.setDataSource(dataSource()); + return repos; + } + + @Bean + DataSource dataSource() { + return new EmbeddedDatabaseBuilder() + .setType(EmbeddedDatabaseType.HSQL) + .build(); + } + } + + + interface FooRepository { + List findAll(); + } + + + @Repository + static class JdbcFooRepository implements FooRepository { + + public void setDataSource(DataSource dataSource) { + } + + @Transactional + public List findAll() { + return Collections.emptyList(); + } + } + + @Repository + static class DummyFooRepository implements FooRepository { + + @Transactional + public List findAll() { + return Collections.emptyList(); + } + } +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java new file mode 100644 index 00000000000..eca12ccb2eb --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.Assert; + +/** + * Abstract base class providing common structure for enabling Spring's annotation- + * driven transaction management capability. + * + * @author Chris Beams + * @since 3.1 + * @see EnableTransactionManagement + */ +@Configuration +public abstract class AbstractTransactionManagementConfiguration implements ImportAware { + + protected Map enableTx; + protected PlatformTransactionManager txManager; + + public void setImportMetadata(AnnotationMetadata importMetadata) { + enableTx = importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false); + Assert.notNull(enableTx, + "@EnableTransactionManagement is not present on importing class " + + importMetadata.getClassName()); + } + + @Autowired(required=false) + void setConfigurers(Collection configurers) { + if (configurers == null || configurers.isEmpty()) { + return; + } + + if (configurers.size() > 1) { + throw new IllegalStateException("only one TransactionManagementConfigurer may exist"); + } + + TransactionManagementConfigurer configurer = configurers.iterator().next(); + this.txManager = configurer.createTransactionManager(); + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java new file mode 100644 index 00000000000..eb9def90709 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/EnableTransactionManagement.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; +import org.springframework.context.config.AdviceMode; +import org.springframework.core.Ordered; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TransactionManagementConfigurationSelector.class) +public @interface EnableTransactionManagement { + + /** + * Indicate whether class-based (CGLIB) proxies are to be created as opposed + * to standard Java interface-based proxies. The default is {@code false}. + * + *

Note: Class-based proxies require the {@link Transactional @Transactional} + * annotation to be defined on the concrete class. Annotations in interfaces will + * not work in that case (they will rather only work with interface-based proxies)! + */ + boolean proxyTargetClass() default false; + + /** + * Indicate how transactional advice should be applied. + * The default is {@link AdviceMode.PROXY}. + * @see AdviceMode + */ + AdviceMode mode() default AdviceMode.PROXY; + + /** + * Indicate the ordering of the execution of the transaction advisor + * when multiple advices are applied at a specific joinpoint. + * The default is to not explicitly order the advisor. + */ + int order() default Ordered.NOT_ORDERED; +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java new file mode 100644 index 00000000000..16953a71207 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2011 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.annotation; + +import org.springframework.aop.config.AopConfigUtils; +import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.core.Ordered; +import org.springframework.transaction.config.TransactionManagementConfigUtils; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import org.springframework.transaction.interceptor.TransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +@Configuration +public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + @Bean(name=TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { + BeanFactoryTransactionAttributeSourceAdvisor advisor = + new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource()); + advisor.setAdvice(transactionInterceptor()); + int order = (Integer)this.enableTx.get("order"); + if (order != Ordered.NOT_ORDERED) { + advisor.setOrder(order); + } + return advisor; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(); + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionInterceptor transactionInterceptor() { + TransactionInterceptor interceptor = new TransactionInterceptor(); + interceptor.setTransactionAttributeSource(transactionAttributeSource()); + if (this.txManager != null) { + interceptor.setTransactionManager(this.txManager); + } + return interceptor; + } + + // TODO: deal with escalation of APCs + @Bean(name=AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public InfrastructureAdvisorAutoProxyCreator apc() { + InfrastructureAdvisorAutoProxyCreator apc = new InfrastructureAdvisorAutoProxyCreator(); + apc.setProxyTargetClass((Boolean) this.enableTx.get("proxyTargetClass")); + return apc; + } +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java new file mode 100644 index 00000000000..0fb0ddfc3e0 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2011 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.annotation; + +import java.util.Map; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.context.config.AdviceMode; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; + +public class TransactionManagementConfigurationSelector implements ImportSelector { + + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + Map enableTx = + importingClassMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName()); + Assert.notNull(enableTx, + "@EnableTransactionManagement is not present on importing class " + + importingClassMetadata.getClassName()); + + switch ((AdviceMode) enableTx.get("mode")) { + case PROXY: + return new String[] {ProxyTransactionManagementConfiguration.class.getName()}; + case ASPECTJ: + return new String[] {"org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration"}; + default: + throw new IllegalArgumentException("Unknown AdviceMode " + enableTx.get("mode")); + } + } + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java new file mode 100644 index 00000000000..145db37d12e --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurer.java @@ -0,0 +1,25 @@ +/* + * Copyright 2002-2011 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.annotation; + +import org.springframework.transaction.PlatformTransactionManager; + +public interface TransactionManagementConfigurer { + + PlatformTransactionManager createTransactionManager(); + +} diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java index 44c3b9759a1..8ba614d7983 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java @@ -26,10 +26,9 @@ import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser; -import org.springframework.context.config.FeatureSpecification; -import org.springframework.transaction.annotation.TransactionManagementCapability; -import org.w3c.dom.Element; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import org.springframework.transaction.interceptor.TransactionInterceptor; /** * {@link org.springframework.beans.factory.xml.BeanDefinitionParser @@ -53,20 +52,20 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { /** * The bean name of the internally managed transaction advisor (mode="proxy"). * @deprecated as of Spring 3.1 in favor of - * {@link TransactionManagementCapability#TRANSACTION_ADVISOR_BEAN_NAME} + * {@link TransactionManagementConfigUtils#TRANSACTION_ADVISOR_BEAN_NAME} */ @Deprecated public static final String TRANSACTION_ADVISOR_BEAN_NAME = - TransactionManagementCapability.TRANSACTION_ADVISOR_BEAN_NAME; + TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; /** * The bean name of the internally managed transaction aspect (mode="aspectj"). * @deprecated as of Spring 3.1 in favor of - * {@link TransactionManagementCapability#TRANSACTION_ASPECT_BEAN_NAME} + * {@link TransactionManagementConfigUtils#TRANSACTION_ASPECT_BEAN_NAME} */ @Deprecated public static final String TRANSACTION_ASPECT_BEAN_NAME = - TransactionManagementCapability.TRANSACTION_ASPECT_BEAN_NAME; + TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME; /** @@ -74,12 +73,81 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator} * with the container as necessary. */ - @Override - protected FeatureSpecification doParse(Element element, ParserContext parserContext) { - return new TxAnnotationDriven(element.getAttribute("transaction-manager")) - .order(element.getAttribute("order")) - .mode(element.getAttribute("mode")) - .proxyTargetClass(Boolean.valueOf(element.getAttribute("proxy-target-class"))); + public BeanDefinition parse(Element element, ParserContext parserContext) { + String mode = element.getAttribute("mode"); + if ("aspectj".equals(mode)) { + // mode="aspectj" + registerTransactionAspect(element, parserContext); + } + else { + // mode="proxy" + AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext); + } + return null; + } + + private void registerTransactionAspect(Element element, ParserContext parserContext) { + String txAspectBeanName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME; + String txAspectClassName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_CLASS_NAME; + if (!parserContext.getRegistry().containsBeanDefinition(txAspectBeanName)) { + RootBeanDefinition def = new RootBeanDefinition(); + def.setBeanClassName(txAspectClassName); + def.setFactoryMethodName("aspectOf"); + registerTransactionManager(element, def); + parserContext.registerBeanComponent(new BeanComponentDefinition(def, txAspectBeanName)); + } + } + + private static void registerTransactionManager(Element element, BeanDefinition def) { + def.getPropertyValues().add("transactionManagerBeanName", + TxNamespaceHandler.getTransactionManagerName(element)); + } + + + /** + * Inner class to just introduce an AOP framework dependency when actually in proxy mode. + */ + private static class AopAutoProxyConfigurer { + + public static void configureAutoProxyCreator(Element element, ParserContext parserContext) { + AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); + + String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; + if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { + Object eleSource = parserContext.extractSource(element); + + // Create the TransactionAttributeSource definition. + RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationTransactionAttributeSource.class); + sourceDef.setSource(eleSource); + sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); + + // Create the TransactionInterceptor definition. + RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); + interceptorDef.setSource(eleSource); + interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registerTransactionManager(element, interceptorDef); + interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); + String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); + + // Create the TransactionAttributeSourceAdvisor definition. + RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); + advisorDef.setSource(eleSource); + advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); + advisorDef.getPropertyValues().add("adviceBeanName", interceptorName); + if (element.hasAttribute("order")) { + advisorDef.getPropertyValues().add("order", element.getAttribute("order")); + } + parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef); + + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); + compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); + compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName)); + parserContext.registerComponent(compositeDef); + } + } } } diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TransactionManagementConfigUtils.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TransactionManagementConfigUtils.java new file mode 100644 index 00000000000..f2a613ed627 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TransactionManagementConfigUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2011 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.config; + +public abstract class TransactionManagementConfigUtils { + + /** + * The bean name of the internally managed transaction advisor (used when mode == PROXY). + */ + public static final String TRANSACTION_ADVISOR_BEAN_NAME = + "org.springframework.transaction.config.internalTransactionAdvisor"; + + /** + * The bean name of the internally managed transaction aspect (used when mode == ASPECTJ). + */ + public static final String TRANSACTION_ASPECT_BEAN_NAME = + "org.springframework.transaction.config.internalTransactionAspect"; + + /** + * The class name of the AspectJ transaction management aspect. + */ + public static final String TRANSACTION_ASPECT_CLASS_NAME = + "org.springframework.transaction.aspectj.AnnotationTransactionAspect"; + +} diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java new file mode 100644 index 00000000000..4f90653f158 --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2011 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.annotation; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.aop.support.AopUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.config.AdviceMode; +import org.springframework.stereotype.Service; +import org.springframework.transaction.CallCountingTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests.TransactionalTestBean; + +/** + * Tests demonstrating use of @EnableTransactionManagement @Configuration classes. + * + * @author Chris Beams + * @since 3.1 + */ +public class EnableTransactionManagementTests { + + @Test + public void transactionProxyIsCreated() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(EnableTxConfig.class, TxManagerConfig.class); + ctx.refresh(); + TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class); + assertThat("testBean is not a proxy", AopUtils.isAopProxy(bean), is(true)); + Map services = ctx.getBeansWithAnnotation(Service.class); + assertThat("Stereotype annotation not visible", services.containsKey("testBean"), is(true)); + } + + @Test + public void txManagerIsResolvedOnInvocationOfTransactionalMethod() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(EnableTxConfig.class, TxManagerConfig.class); + ctx.refresh(); + TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class); + + // invoke a transactional method, causing the PlatformTransactionManager bean to be resolved. + bean.findAllFoos(); + } + + @Test + public void txManagerIsResolvedCorrectlyWhenMultipleManagersArePresent() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(EnableTxConfig.class, MultiTxManagerConfig.class); + ctx.refresh(); + TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class); + + // invoke a transactional method, causing the PlatformTransactionManager bean to be resolved. + bean.findAllFoos(); + } + + /** + * A cheap test just to prove that in ASPECTJ mode, the AnnotationTransactionAspect does indeed + * get loaded -- or in this case, attempted to be loaded at which point the test fails. + */ + @Test + public void proxyTypeAspectJCausesRegistrationOfAnnotationTransactionAspect() { + try { + new AnnotationConfigApplicationContext(EnableAspectJTxConfig.class, TxManagerConfig.class); + fail("should have thrown CNFE when trying to load AnnotationTransactionAspect. " + + "Do you actually have org.springframework.aspects on the classpath?"); + } catch (Exception ex) { + System.out.println(ex); + assertThat(ex.getMessage().endsWith("AspectJTransactionManagementConfiguration.class] cannot be opened because it does not exist"), is(true)); + } + } + + + @Configuration + @EnableTransactionManagement + static class EnableTxConfig { + } + + + @Configuration + @EnableTransactionManagement(mode=AdviceMode.ASPECTJ) + static class EnableAspectJTxConfig { + } + + + @Configuration + static class TxManagerConfig { + + @Bean + public TransactionalTestBean testBean() { + return new TransactionalTestBean(); + } + + @Bean + public PlatformTransactionManager txManager() { + return new CallCountingTransactionManager(); + } + + } + + + @Configuration + static class MultiTxManagerConfig extends TxManagerConfig implements TransactionManagementConfigurer { + + @Bean + public PlatformTransactionManager txManager2() { + return new CallCountingTransactionManager(); + } + + public PlatformTransactionManager createTransactionManager() { + return txManager2(); + } + } +}