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 8674fc5c82e..f2fae6d6737 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 @@ -1996,12 +1996,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(); @@ -2010,13 +2013,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 { @@ -2026,6 +2028,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 80a4a53abc1..b8eca28a388 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 @@ -1718,18 +1718,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 @@ -3519,6 +3574,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 { }