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:
parent
48221798f5
commit
5fe8f52c02
|
@ -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") {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue