From c6146ea2db8ca1e2b7a0b0ddef00ce413b151e04 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 16 Feb 2024 21:28:04 +0100 Subject: [PATCH] Introduce shortcut for declared dependency name matching target bean name Closes gh-28122 --- .../support/DefaultListableBeanFactory.java | 94 ++++++++++++++----- .../DefaultListableBeanFactoryTests.java | 17 ---- 2 files changed, 73 insertions(+), 38 deletions(-) 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 ae6019260280..e71a526d8561 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.IdentityHashMap; import java.util.Iterator; @@ -167,6 +168,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Map from bean name to merged BeanDefinitionHolder. */ private final Map mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256); + // Set of bean definition names with a primary marker. */ + private final Set primaryBeanNames = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); + /** Map of singleton and non-singleton bean names, keyed by dependency type. */ private final Map, String[]> allBeanNamesByType = new ConcurrentHashMap<>(64); @@ -1084,6 +1088,11 @@ else if (!beanDefinition.equals(existingDefinition)) { else if (isConfigurationFrozen()) { clearByTypeCache(); } + + // Cache a primary marker for the given bean. + if (beanDefinition.isPrimary()) { + this.primaryBeanNames.add(beanName); + } } @Override @@ -1135,6 +1144,9 @@ protected void resetBeanDefinition(String beanName) { // (e.g. the default StaticMessageSource in a StaticApplicationContext). destroySingleton(beanName); + // Remove a cached primary marker for the given bean. + this.primaryBeanNames.remove(beanName); + // Notify all post-processors that the specified bean definition has been reset. for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) { processor.resetBeanDefinition(beanName); @@ -1388,15 +1400,27 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str } } - // Step 3a: multiple beans as stream / array / standard collection / plain map + // Step 3: shortcut for declared dependency name matching target bean name + String dependencyName = descriptor.getDependencyName(); + if (dependencyName != null && containsBean(dependencyName) && + isTypeMatch(dependencyName, type) && isAutowireCandidate(dependencyName, descriptor) && + !hasPrimaryConflict(dependencyName, type) && !isSelfReference(beanName, dependencyName)) { + if (autowiredBeanNames != null) { + autowiredBeanNames.add(dependencyName); + } + Object dependencyBean = getBean(dependencyName); + return resolveInstance(dependencyBean, descriptor, type, dependencyName); + } + + // Step 4a: multiple beans as stream / array / standard collection / plain map Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } - // Step 3b: direct bean matches, possibly direct beans of type Collection / Map + // Step 4b: direct bean matches, possibly direct beans of type Collection / Map Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { - // Step 3c (fallback): custom Collection / Map declarations for collecting multiple beans + // Step 4c (fallback): custom Collection / Map declarations for collecting multiple beans multipleBeans = resolveMultipleBeansFallback(descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; @@ -1411,7 +1435,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str String autowiredBeanName; Object instanceCandidate; - // Step 4: determine single candidate + // Step 5: determine single candidate if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { @@ -1435,31 +1459,37 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str instanceCandidate = entry.getValue(); } - // Step 5: validate single result + // Step 6: validate single result if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } - Object result = instanceCandidate; - if (result instanceof NullBean) { - if (isRequired(descriptor)) { - // Raise exception if null encountered for required injection point - raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); - } - result = null; - } - if (!ClassUtils.isAssignableValue(type, result)) { - throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass()); - } - return result; + return resolveInstance(instanceCandidate, descriptor, type, autowiredBeanName); } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } } + @Nullable + private Object resolveInstance(Object candidate, DependencyDescriptor descriptor, Class type, String name) { + Object result = candidate; + if (result instanceof NullBean) { + // Raise exception if null encountered for required injection point + if (isRequired(descriptor)) { + raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); + } + result = null; + } + if (!ClassUtils.isAssignableValue(type, result)) { + throw new BeanNotOfRequiredTypeException(name, type, candidate.getClass()); + } + return result; + + } + @Nullable private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { @@ -1712,20 +1742,27 @@ else if (containsSingleton(candidateName) || (descriptor instanceof StreamDepend @Nullable protected String determineAutowireCandidate(Map candidates, DependencyDescriptor descriptor) { Class requiredType = descriptor.getDependencyType(); + // Step 1: check primary candidate String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); if (primaryCandidate != null) { return primaryCandidate; } + // Step 2: check bean name match + for (String candidateName : candidates.keySet()) { + if (matchesBeanName(candidateName, descriptor.getDependencyName())) { + return candidateName; + } + } + // Step 3: check highest priority candidate String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); if (priorityCandidate != null) { return priorityCandidate; } - // Fallback: pick directly registered dependency or qualified bean name match + // Step 4: pick directly registered dependency for (Map.Entry entry : candidates.entrySet()) { String candidateName = entry.getKey(); Object beanInstance = entry.getValue(); - if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) || - matchesBeanName(candidateName, descriptor.getDependencyName())) { + if (beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) { return candidateName; } } @@ -1866,6 +1903,21 @@ private boolean isSelfReference(@Nullable String beanName, @Nullable String cand beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName())))); } + /** + * Determine whether there is a primary bean registered for the given dependency type, + * not matching the given bean name. + */ + @Nullable + private boolean hasPrimaryConflict(String beanName, Class dependencyType) { + for (String candidate : this.primaryBeanNames) { + if (isTypeMatch(candidate, dependencyType) && !candidate.equals(beanName)) { + return true; + } + } + return (getParentBeanFactory() instanceof DefaultListableBeanFactory parent && + parent.hasPrimaryConflict(beanName, dependencyType)); + } + /** * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException * for an unresolvable dependency. 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 501311bd2195..e0a37fb2ff26 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 @@ -18,9 +18,7 @@ import java.io.Closeable; import java.io.Serializable; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.text.NumberFormat; import java.text.ParseException; @@ -81,7 +79,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; -import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; @@ -121,20 +118,6 @@ class DefaultListableBeanFactoryTests { private final DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); - { - // No parameter name discovery expected unless named arguments are used - lbf.setParameterNameDiscoverer(new ParameterNameDiscoverer() { - @Override - public String[] getParameterNames(Method method) { - throw new UnsupportedOperationException(); - } - @Override - public String[] getParameterNames(Constructor ctor) { - throw new UnsupportedOperationException(); - } - }); - } - @Test void unreferencedSingletonWasInstantiated() {