From 6c6004a93b7bad4644b5b04ec97a25e5ce24f0f4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 26 May 2010 09:46:03 +0000 Subject: [PATCH] @Transactional qualifier value matches against @Qualifier annotations on @Bean methods as well (SPR-7232) --- .../interceptor/TransactionAspectUtils.java | 55 ++++++++++++++----- .../config/AnnotationDrivenTests.java | 20 +++++++ .../TransactionManagerConfiguration.java | 45 +++++++++++++++ ...nnotationDrivenConfigurationClassTests.xml | 25 +++++++++ 4 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java create mode 100644 org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenConfigurationClassTests.xml diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java index de87a67f784..78a8a0af998 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java @@ -16,6 +16,7 @@ package org.springframework.transaction.interceptor; +import java.lang.reflect.Method; import java.util.Map; import org.springframework.beans.factory.BeanFactory; @@ -25,6 +26,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.ObjectUtils; @@ -64,7 +66,7 @@ public abstract class TransactionAspectUtils { /** * Obtain a PlatformTransactionManager from the given BeanFactory, * matching the given qualifier. - * @param beanFactory the BeanFactory to get the PlatformTransactionManager bean from + * @param bf the BeanFactory to get the PlatformTransactionManager bean from * @param qualifier the qualifier for selecting between multiple PlatformTransactionManager matches * @return the chosen PlatformTransactionManager (never null) * @throws IllegalStateException if no matching PlatformTransactionManager bean found @@ -74,20 +76,12 @@ public abstract class TransactionAspectUtils { BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PlatformTransactionManager.class); PlatformTransactionManager chosen = null; for (String beanName : tms.keySet()) { - if (bf.containsBeanDefinition(beanName)) { - BeanDefinition bd = bf.getBeanDefinition(beanName); - if (bd instanceof AbstractBeanDefinition) { - AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; - AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); - if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) || - qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) { - if (chosen != null) { - throw new IllegalStateException("No unique PlatformTransactionManager bean found " + - "for qualifier '" + qualifier + "'"); - } - chosen = tms.get(beanName); - } + if (isQualifierMatch(qualifier, beanName, bf)) { + if (chosen != null) { + throw new IllegalStateException("No unique PlatformTransactionManager bean found " + + "for qualifier '" + qualifier + "'"); } + chosen = tms.get(beanName); } } if (chosen != null) { @@ -99,4 +93,37 @@ public abstract class TransactionAspectUtils { } } + /** + * Check whether we have a qualifier match for the given candidate bean. + * @param qualifier the qualifier that we are looking for + * @param beanName the name of the candidate bean + * @param bf the BeanFactory to get the bean definition from + * @return true if either the bean definition (in the XML case) + * or the bean's factory method (in the @Bean case) defines a matching qualifier + * value (through <qualifier<> or @Qualifier) + */ + private static boolean isQualifierMatch(String qualifier, String beanName, ConfigurableListableBeanFactory bf) { + if (bf.containsBeanDefinition(beanName)) { + BeanDefinition bd = bf.getMergedBeanDefinition(beanName); + if (bd instanceof AbstractBeanDefinition) { + AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; + AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); + if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) || + qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) { + return true; + } + } + if (bd instanceof RootBeanDefinition) { + Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod(); + if (factoryMethod != null) { + Qualifier targetAnnotation = factoryMethod.getAnnotation(Qualifier.class); + if (targetAnnotation != null && qualifier.equals(targetAnnotation.value())) { + return true; + } + } + } + } + return false; + } + } diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java index 1d3a237d839..68a0048f8f7 100644 --- a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java @@ -54,6 +54,26 @@ public class AnnotationDrivenTests extends TestCase { assertEquals(2, tm2.commits); } + public void testWithConfigurationClass() throws Exception { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annotationDrivenConfigurationClassTests.xml", getClass()); + CallCountingTransactionManager tm1 = context.getBean("transactionManager1", CallCountingTransactionManager.class); + CallCountingTransactionManager tm2 = context.getBean("transactionManager2", CallCountingTransactionManager.class); + TransactionalService service = context.getBean("service", TransactionalService.class); + assertTrue(AopUtils.isCglibProxy(service)); + service.setSomething("someName"); + assertEquals(1, tm1.commits); + assertEquals(0, tm2.commits); + service.doSomething(); + assertEquals(1, tm1.commits); + assertEquals(1, tm2.commits); + service.setSomething("someName"); + assertEquals(2, tm1.commits); + assertEquals(1, tm2.commits); + service.doSomething(); + assertEquals(2, tm1.commits); + assertEquals(2, tm2.commits); + } + public void testSerializableWithPreviousUsage() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annotationDrivenProxyTargetClassTests.xml", getClass()); TransactionalService service = context.getBean("service", TransactionalService.class); diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java new file mode 100644 index 00000000000..bed910d0e30 --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionManagerConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2010 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; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.CallCountingTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * @author Juergen Hoeller + */ +@Configuration +public class TransactionManagerConfiguration { + + @Bean + @Qualifier("synch") + public PlatformTransactionManager transactionManager1() { + return new CallCountingTransactionManager(); + } + + @Bean + @Qualifier("noSynch") + public PlatformTransactionManager transactionManager2() { + CallCountingTransactionManager tm = new CallCountingTransactionManager(); + tm.setTransactionSynchronization(CallCountingTransactionManager.SYNCHRONIZATION_NEVER); + return tm; + } + +} diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenConfigurationClassTests.xml b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenConfigurationClassTests.xml new file mode 100644 index 00000000000..326cd558ac1 --- /dev/null +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenConfigurationClassTests.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + +