Add support for @Priority

This commit adds support for @Priority to filter multiple candidates
for autowiring. When multiple candidates are available for a given
bean, the bean annotated with @Primary is used. If none exists, the
one with the higher value for the @Priority annotation is used. If
two beans have the same priority a NoUniqueBeanDefinitionException
is thrown, just as if two beans are annotated with @Primary.

The underlying code for #getBean and #resolveDependency has been
merged as this feature is available for both dependency injection
and bean lookup by type.

Issue: SPR-10548
This commit is contained in:
Stephane Nicoll 2014-01-14 14:22:31 +01:00
parent 48221798f5
commit 5fe8f52c02
3 changed files with 199 additions and 24 deletions

View File

@ -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") {

View File

@ -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<String, Object> candidates = new HashMap<String, Object>();
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.
* <p>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<String, Object> candidateBeans, DependencyDescriptor descriptor) {
protected String determineAutowireCandidate(Map<String, Object> 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<String, Object> 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<String, Object> candidateBeans, Class<?> requiredType) {
String primaryBeanName = null;
String fallbackBeanName = null;
for (Map.Entry<String, Object> 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<String, Object> candidateBeans,
Class<?> requiredType) {
String highestPriorityBeanName = null;
Integer highestPriority = null;
for (Map.Entry<String, Object> 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.
* <p>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.

View File

@ -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 {}
}