diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 17f0fc164dc..67858633d77 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1776,12 +1776,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto * @param requiredType the target dependency type to match against * @return the name of the candidate with the highest priority, * or {@code null} if none found + * @throws NoUniqueBeanDefinitionException if multiple beans are detected with + * the same highest priority value * @see #getPriority(Object) */ @Nullable protected String determineHighestPriorityCandidate(Map candidates, Class requiredType) { String highestPriorityBeanName = null; Integer highestPriority = null; + boolean highestPriorityConflictDetected = false; for (Map.Entry entry : candidates.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); @@ -1790,13 +1793,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (candidatePriority != null) { if (highestPriority != null) { if (candidatePriority.equals(highestPriority)) { - throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), - "Multiple beans found with the same priority ('" + highestPriority + - "') among candidates: " + candidates.keySet()); + highestPriorityConflictDetected = true; } else if (candidatePriority < highestPriority) { highestPriorityBeanName = candidateBeanName; highestPriority = candidatePriority; + highestPriorityConflictDetected = false; } } else { @@ -1806,6 +1808,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } } + + if (highestPriorityConflictDetected) { + throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(), + "Multiple beans found with the same highest priority (" + highestPriority + + ") among candidates: " + candidates.keySet()); + + } return highestPriorityBeanName; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 3f350cdbb0e..7687b17323b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -1729,18 +1729,73 @@ class DefaultListableBeanFactoryTests { assertThat(bean.getBeanName()).isEqualTo("bd1"); } + /** + * {@code determineHighestPriorityCandidate()} should reject duplicate + * priorities for the highest priority detected. + * + * @see #getBeanByTypeWithMultipleNonHighestPriorityCandidates() + */ @Test - void getBeanByTypeWithMultiplePriority() { + void getBeanByTypeWithMultipleHighestPriorityCandidates() { lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class); - RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(LowPriorityTestBean.class); + RootBeanDefinition bd3 = new RootBeanDefinition(HighPriorityTestBean.class); lbf.registerBeanDefinition("bd1", bd1); lbf.registerBeanDefinition("bd2", bd2); + lbf.registerBeanDefinition("bd3", bd3); assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(() -> lbf.getBean(TestBean.class)) - .withMessageContaining("Multiple beans found with the same priority") - .withMessageContaining("5"); // conflicting priority + .withMessageContaining("Multiple beans found with the same highest priority (5) among candidates: "); + } + + /** + * {@code determineHighestPriorityCandidate()} should ignore duplicate + * priorities for any priority other than the highest, and the order in + * which beans is declared should not affect the outcome. + * + * @see #getBeanByTypeWithMultipleHighestPriorityCandidates() + */ + @Test // gh-33733 + void getBeanByTypeWithMultipleNonHighestPriorityCandidates() { + getBeanByTypeWithMultipleNonHighestPriorityCandidates( + PriorityService1.class, + PriorityService2A.class, + PriorityService2B.class, + PriorityService3.class + ); + + getBeanByTypeWithMultipleNonHighestPriorityCandidates( + PriorityService3.class, + PriorityService2B.class, + PriorityService2A.class, + PriorityService1.class + ); + + getBeanByTypeWithMultipleNonHighestPriorityCandidates( + PriorityService2A.class, + PriorityService1.class, + PriorityService2B.class, + PriorityService3.class + ); + + getBeanByTypeWithMultipleNonHighestPriorityCandidates( + PriorityService2A.class, + PriorityService3.class, + PriorityService1.class, + PriorityService2B.class + ); + } + + private void getBeanByTypeWithMultipleNonHighestPriorityCandidates(Class... classes) { + lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + for (Class clazz : classes) { + lbf.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz)); + } + + PriorityService bean = lbf.getBean(PriorityService.class); + assertThat(bean).isExactlyInstanceOf(PriorityService1.class); } @Test @@ -3500,6 +3555,26 @@ class DefaultListableBeanFactoryTests { } + interface PriorityService { + } + + @Priority(1) + static class PriorityService1 implements PriorityService { + } + + @Priority(2) + static class PriorityService2A implements PriorityService { + } + + @Priority(2) + static class PriorityService2B implements PriorityService { + } + + @Priority(3) + static class PriorityService3 implements PriorityService { + } + + @Priority(5) private static class HighPriorityTestBean extends TestBean { }