Skip to content

Commit ff72652

Browse files
committedJan 13, 2025
Defensively check expected type for qualified bean
Closes gh-34187
1 parent a1503a5 commit ff72652

File tree

3 files changed

+76
-29
lines changed

3 files changed

+76
-29
lines changed
 

Diff for: ‎spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,7 +95,7 @@ public static <T> T qualifiedBeanOfType(BeanFactory beanFactory, Class<T> beanTy
9595
// Full qualifier matching supported.
9696
return qualifiedBeanOfType(lbf, beanType, qualifier);
9797
}
98-
else if (beanFactory.containsBean(qualifier)) {
98+
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
9999
// Fallback: target bean at least found by bean name.
100100
return beanFactory.getBean(qualifier, beanType);
101101
}
@@ -110,28 +110,28 @@ else if (beanFactory.containsBean(qualifier)) {
110110
/**
111111
* Obtain a bean of type {@code T} from the given {@code BeanFactory} declaring a qualifier
112112
* (for example, {@code <qualifier>} or {@code @Qualifier}) matching the given qualifier).
113-
* @param bf the factory to get the target bean from
113+
* @param beanFactory the factory to get the target bean from
114114
* @param beanType the type of bean to retrieve
115115
* @param qualifier the qualifier for selecting between multiple bean matches
116116
* @return the matching bean of type {@code T} (never {@code null})
117117
*/
118-
private static <T> T qualifiedBeanOfType(ListableBeanFactory bf, Class<T> beanType, String qualifier) {
119-
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(bf, beanType);
118+
private static <T> T qualifiedBeanOfType(ListableBeanFactory beanFactory, Class<T> beanType, String qualifier) {
119+
String[] candidateBeans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, beanType);
120120
String matchingBean = null;
121121
for (String beanName : candidateBeans) {
122-
if (isQualifierMatch(qualifier::equals, beanName, bf)) {
122+
if (isQualifierMatch(qualifier::equals, beanName, beanFactory)) {
123123
if (matchingBean != null) {
124124
throw new NoUniqueBeanDefinitionException(beanType, matchingBean, beanName);
125125
}
126126
matchingBean = beanName;
127127
}
128128
}
129129
if (matchingBean != null) {
130-
return bf.getBean(matchingBean, beanType);
130+
return beanFactory.getBean(matchingBean, beanType);
131131
}
132-
else if (bf.containsBean(qualifier)) {
132+
else if (beanFactory.containsBean(qualifier) && beanFactory.isTypeMatch(qualifier, beanType)) {
133133
// Fallback: target bean at least found by bean name - probably a manually registered singleton.
134-
return bf.getBean(qualifier, beanType);
134+
return beanFactory.getBean(qualifier, beanType);
135135
}
136136
else {
137137
throw new NoSuchBeanDefinitionException(qualifier, "No matching " + beanType.getSimpleName() +

Diff for: ‎spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java

+65-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -58,6 +58,7 @@
5858
* @author Juergen Hoeller
5959
* @author Stephane Nicoll
6060
* @author Sam Brannen
61+
* @author Yanming Zhou
6162
* @since 3.1
6263
*/
6364
class EnableTransactionManagementTests {
@@ -243,8 +244,8 @@ void transactionalEventListenerRegisteredProperly() {
243244
}
244245

245246
@Test
246-
void spr11915TransactionManagerAsManualSingleton() {
247-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr11915Config.class);
247+
void transactionManagerAsManualSingleton() {
248+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ManualSingletonConfig.class);
248249
TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
249250
CallCountingTransactionManager txManager = ctx.getBean("qualifiedTransactionManager", CallCountingTransactionManager.class);
250251

@@ -264,25 +265,49 @@ void spr11915TransactionManagerAsManualSingleton() {
264265
}
265266

266267
@Test
267-
void gh24291TransactionManagerViaQualifierAnnotation() {
268-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Gh24291Config.class);
269-
TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
270-
CallCountingTransactionManager txManager = ctx.getBean("qualifiedTransactionManager", CallCountingTransactionManager.class);
268+
void transactionManagerViaQualifierAnnotation() {
269+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(QualifiedTransactionConfig.class);
270+
271+
TransactionalTestBean bean = ctx.getBean("testBean", TransactionalTestBean.class);
272+
TransactionalTestBeanWithNonExistentQualifier beanWithNonExistentQualifier = ctx.getBean(
273+
"testBeanWithNonExistentQualifier", TransactionalTestBeanWithNonExistentQualifier.class);
274+
TransactionalTestBeanWithInvalidQualifier beanWithInvalidQualifier = ctx.getBean(
275+
"testBeanWithInvalidQualifier", TransactionalTestBeanWithInvalidQualifier.class);
276+
277+
CallCountingTransactionManager qualified = ctx.getBean("qualifiedTransactionManager",
278+
CallCountingTransactionManager.class);
279+
CallCountingTransactionManager primary = ctx.getBean("primaryTransactionManager",
280+
CallCountingTransactionManager.class);
271281

272282
bean.saveQualifiedFoo();
273-
assertThat(txManager.begun).isEqualTo(1);
274-
assertThat(txManager.commits).isEqualTo(1);
275-
assertThat(txManager.rollbacks).isEqualTo(0);
283+
assertThat(qualified.begun).isEqualTo(1);
284+
assertThat(qualified.commits).isEqualTo(1);
285+
assertThat(qualified.rollbacks).isEqualTo(0);
276286

277287
bean.saveQualifiedFooWithAttributeAlias();
278-
assertThat(txManager.begun).isEqualTo(2);
279-
assertThat(txManager.commits).isEqualTo(2);
280-
assertThat(txManager.rollbacks).isEqualTo(0);
288+
assertThat(qualified.begun).isEqualTo(2);
289+
assertThat(qualified.commits).isEqualTo(2);
290+
assertThat(qualified.rollbacks).isEqualTo(0);
281291

282292
bean.findAllFoos();
283-
assertThat(txManager.begun).isEqualTo(3);
284-
assertThat(txManager.commits).isEqualTo(3);
285-
assertThat(txManager.rollbacks).isEqualTo(0);
293+
assertThat(qualified.begun).isEqualTo(3);
294+
assertThat(qualified.commits).isEqualTo(3);
295+
assertThat(qualified.rollbacks).isEqualTo(0);
296+
297+
beanWithNonExistentQualifier.findAllFoos();
298+
assertThat(primary.begun).isEqualTo(1);
299+
assertThat(primary.commits).isEqualTo(1);
300+
assertThat(primary.rollbacks).isEqualTo(0);
301+
302+
beanWithInvalidQualifier.findAllFoos();
303+
assertThat(primary.begun).isEqualTo(2);
304+
assertThat(primary.commits).isEqualTo(2);
305+
assertThat(primary.rollbacks).isEqualTo(0);
306+
307+
// no further access to qualified transaction manager
308+
assertThat(qualified.begun).isEqualTo(3);
309+
assertThat(qualified.commits).isEqualTo(3);
310+
assertThat(qualified.rollbacks).isEqualTo(0);
286311

287312
ctx.close();
288313
}
@@ -386,6 +411,16 @@ public void saveQualifiedFooWithAttributeAlias() {
386411
public static class TransactionalTestBeanSubclass extends TransactionalTestBean {
387412
}
388413

414+
@Service
415+
@Qualifier("nonExistentBean")
416+
public static class TransactionalTestBeanWithNonExistentQualifier extends TransactionalTestBean {
417+
}
418+
419+
@Service
420+
@Qualifier("transactionalTestBeanWithInvalidQualifier")
421+
public static class TransactionalTestBeanWithInvalidQualifier extends TransactionalTestBean {
422+
}
423+
389424

390425
@Configuration
391426
static class PlaceholderConfig {
@@ -558,7 +593,7 @@ public PlatformTransactionManager annotationDrivenTransactionManager() {
558593
@Configuration
559594
@EnableTransactionManagement
560595
@Import(PlaceholderConfig.class)
561-
static class Spr11915Config {
596+
static class ManualSingletonConfig {
562597

563598
@Autowired
564599
public void initializeApp(ConfigurableApplicationContext applicationContext) {
@@ -581,7 +616,7 @@ public CallCountingTransactionManager otherTxManager() {
581616
@Configuration
582617
@EnableTransactionManagement
583618
@Import(PlaceholderConfig.class)
584-
static class Gh24291Config {
619+
static class QualifiedTransactionConfig {
585620

586621
@Autowired
587622
public void initializeApp(ConfigurableApplicationContext applicationContext) {
@@ -596,7 +631,18 @@ public TransactionalTestBeanSubclass testBean() {
596631
}
597632

598633
@Bean
599-
public CallCountingTransactionManager otherTxManager() {
634+
public TransactionalTestBeanWithNonExistentQualifier testBeanWithNonExistentQualifier() {
635+
return new TransactionalTestBeanWithNonExistentQualifier();
636+
}
637+
638+
@Bean
639+
public TransactionalTestBeanWithInvalidQualifier testBeanWithInvalidQualifier() {
640+
return new TransactionalTestBeanWithInvalidQualifier();
641+
}
642+
643+
@Bean
644+
@Primary
645+
public CallCountingTransactionManager primaryTransactionManager() {
600646
return new CallCountingTransactionManager();
601647
}
602648
}

Diff for: ‎spring-tx/src/test/java/org/springframework/transaction/interceptor/TransactionInterceptorTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -296,6 +296,7 @@ private TransactionInterceptor simpleTransactionInterceptor(BeanFactory beanFact
296296
private PlatformTransactionManager associateTransactionManager(BeanFactory beanFactory, String name) {
297297
PlatformTransactionManager transactionManager = mock();
298298
given(beanFactory.containsBean(name)).willReturn(true);
299+
given(beanFactory.isTypeMatch(name, TransactionManager.class)).willReturn(true);
299300
given(beanFactory.getBean(name, TransactionManager.class)).willReturn(transactionManager);
300301
return transactionManager;
301302
}

0 commit comments

Comments
 (0)
Please sign in to comment.