diff --git a/build.gradle b/build.gradle index d63395d49d..99b0d166ec 100644 --- a/build.gradle +++ b/build.gradle @@ -280,7 +280,8 @@ project("spring-beans") { optional("javax.inject:javax.inject:1") optional("javax.el:javax.el-api:2.2.4") testCompile("log4j:log4j:1.2.17") - } + testCompile("org.apache.tomcat.embed:tomcat-embed-core:8.0.0-RC10") + } } project("spring-beans-groovy") { 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 99f1c5cd8e..8249cd3399 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 @@ -93,6 +93,7 @@ import org.springframework.util.StringUtils; * @author Costin Leau * @author Chris Beams * @author Phillip Webb + * @author Stephane Nicoll * @since 16 April 2001 * @see StaticListableBeanFactory * @see PropertiesBeanDefinitionReader @@ -296,21 +297,19 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return getBean(beanNames[0], requiredType); } else if (beanNames.length > 1) { - T primaryBean = null; + Map candidates = new HashMap(); for (String beanName : beanNames) { - T beanInstance = getBean(beanName, requiredType); - if (isPrimary(beanName, beanInstance)) { - if (primaryBean != null) { - throw new NoUniqueBeanDefinitionException(requiredType, beanNames.length, - "more than one 'primary' bean found of required type: " + Arrays.asList(beanNames)); - } - primaryBean = beanInstance; - } + candidates.put(beanName, getBean(beanName, requiredType)); } - if (primaryBean != null) { - return primaryBean; + String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); + if (primaryCandidate != null) { + return getBean(primaryCandidate, requiredType); } - throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); + if (priorityCandidate != null) { + return getBean(priorityCandidate, requiredType); + } + throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet()); } else if (getParentBeanFactory() != null) { return getParentBeanFactory().getBean(requiredType); @@ -965,7 +964,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return null; } if (matchingBeans.size() > 1) { - String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); + String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (primaryBeanName == null) { throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } @@ -1029,15 +1028,50 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } /** - * Determine the primary autowire candidate in the given set of beans. + * Determine the autowire candidate in the given set of beans. + *

Looks for {@code @Primary} and {@code @Priority} (in that order). + * * @param candidateBeans a Map of candidate names and candidate instances * that match the required type, as returned by {@link #findAutowireCandidates} * @param descriptor the target dependency to match against - * @return the name of the primary candidate, or {@code null} if none found + * @return the name of the autowire candidate, or {@code null} if none found */ - protected String determinePrimaryCandidate(Map candidateBeans, DependencyDescriptor descriptor) { + protected String determineAutowireCandidate(Map candidateBeans, + DependencyDescriptor descriptor) { + Class requiredType = descriptor.getDependencyType(); + String primaryCandidate = + determinePrimaryCandidate(candidateBeans, requiredType); + if (primaryCandidate != null) { + return primaryCandidate; + } + String priorityCandidate = + determineHighestPriorityCandidate(candidateBeans, requiredType); + if (priorityCandidate != null) { + return priorityCandidate; + } + // Fallback + for (Map.Entry entry : candidateBeans.entrySet()) { + String candidateBeanName = entry.getKey(); + Object beanInstance = entry.getValue(); + if (this.resolvableDependencies.values().contains(beanInstance) || + matchesBeanName(candidateBeanName, descriptor.getDependencyName())) { + return candidateBeanName; + } + } + return null; + } + + /** + * Determine the primary candidate in the given set of beans. + * + * @param candidateBeans a Map of candidate names and candidate instances + * that match the required type + * @param requiredType the target dependency type to match against + * @return the name of the primary candidate, or {@code null} if none found + * @see #isPrimary(String, Object) + */ + protected String determinePrimaryCandidate(Map candidateBeans, Class requiredType) { String primaryBeanName = null; - String fallbackBeanName = null; for (Map.Entry entry : candidateBeans.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); @@ -1046,7 +1080,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto boolean candidateLocal = containsBeanDefinition(candidateBeanName); boolean primaryLocal = containsBeanDefinition(primaryBeanName); if (candidateLocal && primaryLocal) { - throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), candidateBeans.size(), + throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(), "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); } else if (candidateLocal) { @@ -1057,13 +1091,44 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto primaryBeanName = candidateBeanName; } } - if (primaryBeanName == null && - (this.resolvableDependencies.values().contains(beanInstance) || - matchesBeanName(candidateBeanName, descriptor.getDependencyName()))) { - fallbackBeanName = candidateBeanName; + } + return primaryBeanName; + } + + /** + * Determine the candidate with the highest priority in the given set of beans. + * + * @param candidateBeans a Map of candidate names and candidate instances + * that match the required type + * @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 + * @see #getPriority(Object) + */ + protected String determineHighestPriorityCandidate(Map candidateBeans, + Class requiredType) { + String highestPriorityBeanName = null; + Integer highestPriority = null; + for (Map.Entry entry : candidateBeans.entrySet()) { + String candidateBeanName = entry.getKey(); + Object beanInstance = entry.getValue(); + Integer candidatePriority = getPriority(beanInstance); + if (candidatePriority != null) { + if (highestPriorityBeanName != null) { + if (candidatePriority.equals(highestPriority)) { + throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(), + "Multiple beans found with the same priority ('" + highestPriority + "') " + + "among candidates: " + candidateBeans.keySet()); + } else if (candidatePriority > highestPriority) { + highestPriorityBeanName = candidateBeanName; + highestPriority = candidatePriority; + } + } else { + highestPriorityBeanName = candidateBeanName; + highestPriority = candidatePriority; + } } } - return (primaryBeanName != null ? primaryBeanName : fallbackBeanName); + return highestPriorityBeanName; } /** @@ -1082,6 +1147,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto ((DefaultListableBeanFactory) parentFactory).isPrimary(beanName, beanInstance)); } + /** + * Return the priority assigned for the given bean instance by + * the {@code javax.annotation.Priority} annotation. + *

If the annotation is not present, returns {@code null}. + * + * @param beanInstance the bean instance to check + * @return the priority assigned to that bean or {@code null} if none is set + */ + protected Integer getPriority(Object beanInstance) { + for (Annotation annotation : beanInstance.getClass().getAnnotations()) { + if ("javax.annotation.Priority".equals(annotation.annotationType().getName())) { + return (Integer) AnnotationUtils.getValue(annotation); + } + } + return null; + } + /** * Determine whether the given candidate name matches the bean name or the aliases * stored in this bean definition. 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 4f2982f9ec..618ad67c2f 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 @@ -32,6 +32,7 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; +import javax.annotation.Priority; import javax.security.auth.Subject; import org.apache.commons.logging.Log; @@ -99,6 +100,7 @@ import static org.mockito.BDDMockito.*; * @author Sam Brannen * @author Chris Beams * @author Phillip Webb + * @author Stephane Nicoll */ public class DefaultListableBeanFactoryTests { @@ -1377,6 +1379,42 @@ public class DefaultListableBeanFactoryTests { lbf.getBean(TestBean.class); } + @Test + public void testGetBeanByTypeWithPriority() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(LowPriorityTestBean.class); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName(), equalTo("bd1")); + } + + @Test + public void testGetBeanByTypeWithMultiplePriority() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + thrown.expect(NoUniqueBeanDefinitionException.class); + thrown.expectMessage(containsString("Multiple beans found with the same priority")); + thrown.expectMessage(containsString("500")); // conflicting priority + lbf.getBean(TestBean.class); + } + + @Test + public void testGetBeanByTypePrimaryHasPrecedenceOverPriority() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setPrimary(true); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName(), equalTo("bd2")); + } + @Test public void testGetBeanByTypeFiltersOutNonAutowireCandidates() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -1546,6 +1584,53 @@ public class DefaultListableBeanFactoryTests { } } + @Test + public void testAutowireBeanByTypeWithTwoMatchesAndPriority() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(LowPriorityTestBean.class); + lbf.registerBeanDefinition("test", bd); + lbf.registerBeanDefinition("spouse", bd2); + + DependenciesBean bean = (DependenciesBean) + lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); + assertThat(bean.getSpouse(), equalTo(lbf.getBean("test"))); + } + + @Test + public void testAutowireBeanByTypeWithIdenticalPriorityCandidates() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class); + lbf.registerBeanDefinition("test", bd); + lbf.registerBeanDefinition("spouse", bd2); + + try { + lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); + fail("Should have thrown UnsatisfiedDependencyException"); + } + catch (UnsatisfiedDependencyException ex) { + // expected + assertNotNull("Exception should have cause", ex.getCause()); + assertEquals("Wrong cause type", NoUniqueBeanDefinitionException.class, ex.getCause().getClass()); + assertTrue(ex.getMessage().contains("500")); // conflicting priority + } + } + + @Test + public void testAutowireBeanByTypePrimaryTakesPrecedenceOverPriority() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd = new RootBeanDefinition(HighPriorityTestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setPrimary(true); + lbf.registerBeanDefinition("test", bd); + lbf.registerBeanDefinition("spouse", bd2); + + DependenciesBean bean = (DependenciesBean) + lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true); + assertThat(bean.getSpouse(), equalTo(lbf.getBean("spouse"))); + } + @Test public void testAutowireExistingBeanByName() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -2802,4 +2887,11 @@ public class DefaultListableBeanFactoryTests { } + @Priority(500) + private static class HighPriorityTestBean extends TestBean {} + + @Priority(5) + private static class LowPriorityTestBean extends TestBean {} + + }