All Downloads are FREE. Search and download functionalities are using the official Maven repository.

infra.beans.factory.support.ConstructorResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 - 2024 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see [https://www.gnu.org/licenses/]
 */

package infra.beans.factory.support;

import java.beans.ConstructorProperties;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import infra.beans.BeanMetadataElement;
import infra.beans.BeanUtils;
import infra.beans.BeanWrapper;
import infra.beans.BeanWrapperImpl;
import infra.beans.BeansException;
import infra.beans.TypeConverter;
import infra.beans.TypeMismatchException;
import infra.beans.factory.BeanCreationException;
import infra.beans.factory.BeanDefinitionStoreException;
import infra.beans.factory.BeanFactory;
import infra.beans.factory.FactoryBean;
import infra.beans.factory.InjectionPoint;
import infra.beans.factory.NoSuchBeanDefinitionException;
import infra.beans.factory.NoUniqueBeanDefinitionException;
import infra.beans.factory.UnsatisfiedDependencyException;
import infra.beans.factory.config.AutowireCapableBeanFactory;
import infra.beans.factory.config.BeanDefinition;
import infra.beans.factory.config.BeanReference;
import infra.beans.factory.config.ConstructorArgumentValues;
import infra.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import infra.beans.factory.config.DependencyDescriptor;
import infra.beans.factory.config.RuntimeBeanReference;
import infra.beans.factory.config.TypedStringValue;
import infra.core.MethodParameter;
import infra.core.NamedThreadLocal;
import infra.core.ParameterNameDiscoverer;
import infra.core.ResolvableType;
import infra.lang.Assert;
import infra.lang.Constant;
import infra.lang.Nullable;
import infra.logging.Logger;
import infra.util.ClassUtils;
import infra.util.CollectionUtils;
import infra.util.ObjectUtils;
import infra.util.ReflectionUtils;
import infra.util.ReflectiveMethodInvoker;
import infra.util.StringUtils;

/**
 * Delegate for resolving constructors and factory methods.
 *
 * 

Performs constructor resolution through argument matching. * * @author Juergen Hoeller * @author Rob Harrop * @author Mark Fisher * @author Costin Leau * @author Sebastien Deleuze * @author Sam Brannen * @author Harry Yang * @author Stephane Nicoll * @author Phil Webb * @see #autowireConstructor * @see #instantiateUsingFactoryMethod * @see #resolveConstructorOrFactoryMethod * @see AbstractAutowireCapableBeanFactory * @since 4.0 2022/1/6 21:16 */ final class ConstructorResolver { private static final Object[] EMPTY_ARGS = Constant.EMPTY_OBJECTS; private static final NamedThreadLocal currentInjectionPoint = new NamedThreadLocal<>("Current injection point"); private final Logger log; private final AbstractAutowireCapableBeanFactory beanFactory; /** * Create a new ConstructorResolver for the given factory and instantiation strategy. * * @param beanFactory the BeanFactory to work with */ public ConstructorResolver(AbstractAutowireCapableBeanFactory beanFactory) { this.beanFactory = beanFactory; this.log = beanFactory.getLogger(); } // BeanWrapper-based construction /** * "autowire constructor" (with constructor arguments by type) behavior. * Also applied if explicit constructor argument values are specified, * matching all remaining arguments with beans from the bean factory. *

This corresponds to constructor injection: In this mode, a Framework * bean factory is able to host components that expect constructor-based * dependency resolution. * * @param beanName the name of the bean * @param merged the merged bean definition for the bean * @param chosenCtors chosen candidate constructors (or {@code null} if none) * @param explicitArgs argument values passed in programmatically via the getBean method, * or {@code null} if none (-> use constructor argument values from bean definition) * @return a BeanWrapper for the new instance */ public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition merged, @Nullable Constructor[] chosenCtors, @Nullable Object[] explicitArgs) { BeanWrapperImpl wrapper = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(wrapper); Constructor constructorToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; if (explicitArgs != null) { argsToUse = explicitArgs; } else { Object[] argsToResolve = null; synchronized(merged.constructorArgumentLock) { constructorToUse = (Constructor) merged.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && merged.constructorArgumentsResolved) { // Found a cached constructor... argsToUse = merged.resolvedConstructorArguments; if (argsToUse == null) { argsToResolve = merged.preparedConstructorArguments; } } } if (argsToResolve != null) { argsToUse = resolvePreparedArguments(beanName, merged, constructorToUse, argsToResolve, wrapper); } } if (constructorToUse == null || argsToUse == null) { // Take specified constructors, if any. Constructor[] candidates = chosenCtors; if (candidates == null) { Class beanClass = merged.getBeanClass(); try { candidates = merged.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors(); } catch (Throwable ex) { throw new BeanCreationException(merged.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } if (candidates.length == 1 && explicitArgs == null && !merged.hasConstructorArgumentValues()) { Constructor uniqueCandidate = candidates[0]; if (uniqueCandidate.getParameterCount() == 0) { synchronized(merged.constructorArgumentLock) { merged.resolvedConstructorOrFactoryMethod = uniqueCandidate; merged.constructorArgumentsResolved = true; merged.resolvedConstructorArguments = EMPTY_ARGS; } wrapper.setBeanInstance(instantiate(beanName, merged, uniqueCandidate, EMPTY_ARGS)); return wrapper; } } // Need to resolve the constructor. boolean autowiring = chosenCtors != null || merged.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR; ConstructorArgumentValues resolvedValues = null; int minNrOfArgs; if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { ConstructorArgumentValues cargs = merged.getConstructorArgumentValues(); resolvedValues = new ConstructorArgumentValues(); minNrOfArgs = resolveConstructorArguments(beanName, merged, wrapper, cargs, resolvedValues); } AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; Set> ambiguousConstructors = null; Deque causes = null; for (Constructor candidate : candidates) { int parameterCount = candidate.getParameterCount(); if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) { // Already found greedy constructor that can be satisfied -> // do not look any further, there are only less greedy constructors left. break; } if (parameterCount < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; Class[] paramTypes = candidate.getParameterTypes(); if (resolvedValues != null) { try { String[] paramNames = null; if (resolvedValues.containsNamedArgument()) { paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); if (paramNames == null) { ParameterNameDiscoverer pnd = beanFactory.getParameterNameDiscoverer(); if (pnd != null) { paramNames = pnd.getParameterNames(candidate); } } } argsHolder = createArgumentArray(beanName, merged, resolvedValues, paramTypes, paramNames, getUserDeclaredConstructor(candidate), wrapper, autowiring, candidates.length == 1); } catch (UnsatisfiedDependencyException ex) { if (log.isTraceEnabled()) { log.trace("Ignoring constructor [{}] of bean '{}': {}", candidate, beanName, ex); } // Swallow and try next constructor. if (causes == null) { causes = new ArrayDeque<>(1); } causes.add(ex); continue; } } else { // Explicit arguments given -> arguments length must match exactly. if (parameterCount != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } int typeDiffWeight = merged.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes); // Choose this constructor if it represents the closest match. if (typeDiffWeight < minTypeDiffWeight) { constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { ambiguousConstructors = new LinkedHashSet<>(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); } } if (constructorToUse == null) { if (causes != null) { UnsatisfiedDependencyException ex = causes.removeLast(); for (Exception cause : causes) { beanFactory.onSuppressedException(cause); } throw ex; } throw new BeanCreationException(merged.getResourceDescription(), beanName, "Could not resolve matching constructor on bean class [" + merged.getBeanClassName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities. " + "You should also check the consistency of arguments when mixing indexed and named arguments, " + "especially in case of bean definition inheritance)"); } else if (ambiguousConstructors != null && !merged.isLenientConstructorResolution()) { throw new BeanCreationException(merged.getResourceDescription(), beanName, "Ambiguous constructor matches found on bean class [" + merged.getBeanClassName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousConstructors); } if (explicitArgs == null && argsHolderToUse != null) { argsHolderToUse.storeCache(merged, constructorToUse); } } Assert.state(argsToUse != null, "Unresolved constructor arguments"); wrapper.setBeanInstance(instantiate(beanName, merged, constructorToUse, argsToUse)); return wrapper; } private Object instantiate(String beanName, RootBeanDefinition merged, Constructor constructorToUse, Object[] argsToUse) { try { return beanFactory.getInstantiationStrategy().instantiate( merged, beanName, beanFactory, constructorToUse, argsToUse); } catch (Throwable ex) { throw new BeanCreationException(merged.getResourceDescription(), beanName, ex.getMessage(), ex); } } /** * Resolve the factory method in the specified bean definition, if possible. * {@link RootBeanDefinition#getResolvedFactoryMethod()} can be checked for the result. * * @param merged the bean definition to check */ public void resolveFactoryMethodIfPossible(RootBeanDefinition merged) { Class factoryClass; boolean isStatic; if (merged.getFactoryBeanName() != null) { factoryClass = beanFactory.getType(merged.getFactoryBeanName()); isStatic = false; } else { factoryClass = merged.getBeanClass(); isStatic = true; } Assert.state(factoryClass != null, "Unresolvable factory class"); factoryClass = ClassUtils.getUserClass(factoryClass); Method[] candidates = getCandidateMethods(factoryClass, merged); Method uniqueCandidate = null; for (Method candidate : candidates) { if ((!isStatic || isStaticCandidate(candidate, factoryClass)) && merged.isFactoryMethod(candidate)) { if (uniqueCandidate == null) { uniqueCandidate = candidate; } else if (isParamMismatch(uniqueCandidate, candidate)) { uniqueCandidate = null; break; } } } merged.factoryMethodToIntrospect = uniqueCandidate; } private static boolean isParamMismatch(Method uniqueCandidate, Method candidate) { int uniqueCandidateParameterCount = uniqueCandidate.getParameterCount(); int candidateParameterCount = candidate.getParameterCount(); return uniqueCandidateParameterCount != candidateParameterCount || !Arrays.equals(uniqueCandidate.getParameterTypes(), candidate.getParameterTypes()); } /** * Retrieve all candidate methods for the given class, considering * the {@link RootBeanDefinition#isNonPublicAccessAllowed()} flag. * Called as the starting point for factory method determination. */ private Method[] getCandidateMethods(Class factoryClass, RootBeanDefinition merged) { return merged.isNonPublicAccessAllowed() ? ReflectionUtils.getUniqueDeclaredMethods(factoryClass) : factoryClass.getMethods(); } private boolean isStaticCandidate(Method method, Class factoryClass) { return (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass() == factoryClass); } /** * Instantiate the bean using a named factory method. The method may be static, if the * bean definition parameter specifies a class, rather than a "factory-bean", or * an instance variable on a factory object itself configured using Dependency Injection. *

Implementation requires iterating over the static or instance methods with the * name specified in the BeanDefinition (the method may be overloaded) and trying * to match with the parameters. We don't have the types attached to constructor args, * so trial and error is the only way to go here. The explicitArgs array may contain * argument values passed in programmatically via the corresponding getBean method. * * @param merged the merged bean definition for the bean * @param explicitArgs argument values passed in programmatically via the getBean * method, or {@code null} if none (-> use constructor argument values from bean definition) * @return a BeanWrapper for the new instance */ public BeanWrapper instantiateUsingFactoryMethod( String beanName, RootBeanDefinition merged, @Nullable Object[] explicitArgs) { BeanWrapperImpl wrapper = new BeanWrapperImpl(); beanFactory.initBeanWrapper(wrapper); boolean isStatic; Object factoryBean; Class factoryClass; String factoryBeanName = merged.getFactoryBeanName(); if (factoryBeanName != null) { if (factoryBeanName.equals(beanName)) { throw new BeanDefinitionStoreException(merged.getResourceDescription(), beanName, "factory-bean reference points back to the same bean definition"); } factoryBean = beanFactory.getBean(factoryBeanName); if (merged.isSingleton() && beanFactory.containsSingleton(beanName)) { throw new ImplicitlyAppearedSingletonException(); } beanFactory.registerDependentBean(factoryBeanName, beanName); factoryClass = factoryBean.getClass(); isStatic = false; } else { // It's a static factory method on the bean class. if (!merged.hasBeanClass()) { throw new BeanDefinitionStoreException(merged.getResourceDescription(), beanName, "bean definition declares neither a bean class nor a factory-bean reference"); } factoryBean = null; factoryClass = merged.getBeanClass(); isStatic = true; } Method factoryMethodToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; if (explicitArgs != null) { argsToUse = explicitArgs; } else { Object[] argsToResolve = null; synchronized(merged.constructorArgumentLock) { factoryMethodToUse = (Method) merged.resolvedConstructorOrFactoryMethod; if (factoryMethodToUse != null && merged.constructorArgumentsResolved) { // Found a cached factory method... argsToUse = merged.resolvedConstructorArguments; if (argsToUse == null) { argsToResolve = merged.preparedConstructorArguments; } } } if (argsToResolve != null) { argsToUse = resolvePreparedArguments(beanName, merged, factoryMethodToUse, argsToResolve, wrapper); } } if (factoryMethodToUse == null || argsToUse == null) { // Need to determine the factory method... // Try all methods with this name to see if they match the given arguments. factoryClass = ClassUtils.getUserClass(factoryClass); List candidates = null; if (merged.isFactoryMethodUnique) { if (factoryMethodToUse == null) { factoryMethodToUse = merged.getResolvedFactoryMethod(); } if (factoryMethodToUse != null) { candidates = Collections.singletonList(factoryMethodToUse); } } if (candidates == null) { candidates = new ArrayList<>(); Method[] rawCandidates = getCandidateMethods(factoryClass, merged); for (Method candidate : rawCandidates) { if ((!isStatic || isStaticCandidate(candidate, factoryClass)) && merged.isFactoryMethod(candidate)) { candidates.add(candidate); } } } if (candidates.size() == 1 && explicitArgs == null && !merged.hasConstructorArgumentValues()) { Method uniqueCandidate = candidates.get(0); if (uniqueCandidate.getParameterCount() == 0) { merged.factoryMethodToIntrospect = uniqueCandidate; synchronized(merged.constructorArgumentLock) { merged.resolvedConstructorOrFactoryMethod = uniqueCandidate; merged.constructorArgumentsResolved = true; merged.resolvedConstructorArguments = EMPTY_ARGS; } wrapper.setBeanInstance(instantiate(beanName, merged, factoryBean, uniqueCandidate, EMPTY_ARGS)); return wrapper; } } if (candidates.size() > 1) { // explicitly skip immutable singletonList candidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR); } ConstructorArgumentValues resolvedValues = null; boolean autowiring = (merged.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); int minTypeDiffWeight = Integer.MAX_VALUE; Set ambiguousFactoryMethods = null; int minNrOfArgs; if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { // We don't have arguments passed in programmatically, so we need to resolve the // arguments specified in the constructor arguments held in the bean definition. if (merged.hasConstructorArgumentValues()) { ConstructorArgumentValues cargs = merged.getConstructorArgumentValues(); resolvedValues = new ConstructorArgumentValues(); minNrOfArgs = resolveConstructorArguments(beanName, merged, wrapper, cargs, resolvedValues); } else { minNrOfArgs = 0; } } Deque causes = null; for (Method candidate : candidates) { int parameterCount = candidate.getParameterCount(); if (parameterCount >= minNrOfArgs) { ArgumentsHolder argsHolder; Class[] paramTypes = candidate.getParameterTypes(); if (explicitArgs != null) { // Explicit arguments given -> arguments length must match exactly. if (paramTypes.length != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } else { // Resolved constructor arguments: type conversion and/or autowiring necessary. try { String[] paramNames = null; if (resolvedValues != null && resolvedValues.containsNamedArgument()) { ParameterNameDiscoverer pnd = beanFactory.getParameterNameDiscoverer(); if (pnd != null) { paramNames = pnd.getParameterNames(candidate); } } argsHolder = createArgumentArray(beanName, merged, resolvedValues, paramTypes, paramNames, candidate, wrapper, autowiring, candidates.size() == 1); } catch (UnsatisfiedDependencyException ex) { if (log.isTraceEnabled()) { log.trace("Ignoring factory method [{}] of bean '{}': {}", candidate, beanName, ex); } // Swallow and try next overloaded factory method. if (causes == null) { causes = new ArrayDeque<>(1); } causes.add(ex); continue; } } int typeDiffWeight = merged.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes); // Choose this factory method if it represents the closest match. if (typeDiffWeight < minTypeDiffWeight) { factoryMethodToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousFactoryMethods = null; } // Find out about ambiguity: In case of the same type difference weight // for methods with the same number of parameters, collect such candidates // and eventually raise an ambiguity exception. // However, only perform that check in non-lenient constructor resolution mode, // and explicitly ignore overridden methods (with the same parameter signature). else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight && !merged.isLenientConstructorResolution() && paramTypes.length == factoryMethodToUse.getParameterCount() && !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) { if (ambiguousFactoryMethods == null) { ambiguousFactoryMethods = new LinkedHashSet<>(); ambiguousFactoryMethods.add(factoryMethodToUse); } ambiguousFactoryMethods.add(candidate); } } } if (factoryMethodToUse == null || argsToUse == null) { if (causes != null) { UnsatisfiedDependencyException ex = causes.removeLast(); for (Exception cause : causes) { beanFactory.onSuppressedException(cause); } throw ex; } ArrayList argTypes = new ArrayList<>(minNrOfArgs); if (explicitArgs != null) { for (Object arg : explicitArgs) { argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null"); } } else if (resolvedValues != null) { var valueHolders = new LinkedHashSet(resolvedValues.getArgumentCount()); valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values()); valueHolders.addAll(resolvedValues.getGenericArgumentValues()); for (ValueHolder value : valueHolders) { String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) : (value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null")); argTypes.add(argType); } } String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes); throw new BeanCreationException(merged.getResourceDescription(), beanName, "No matching factory method found on class [" + factoryClass.getName() + "]: " + (merged.getFactoryBeanName() != null ? "factory bean '" + merged.getFactoryBeanName() + "'; " : "") + "factory method '" + merged.getFactoryMethodName() + "(" + argDesc + ")'. " + "Check that a method with the specified name " + (minNrOfArgs > 0 ? "and arguments " : "") + "exists and that it is " + (isStatic ? "static" : "non-static") + "."); } else if (void.class == factoryMethodToUse.getReturnType()) { throw new BeanCreationException(merged.getResourceDescription(), beanName, "Invalid factory method '" + merged.getFactoryMethodName() + "' on class [" + factoryClass.getName() + "]: needs to have a non-void return type!"); } else if (ambiguousFactoryMethods != null) { throw new BeanCreationException(merged.getResourceDescription(), beanName, "Ambiguous factory method matches found on class [" + factoryClass.getName() + "] " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousFactoryMethods); } if (explicitArgs == null && argsHolderToUse != null) { merged.factoryMethodToIntrospect = factoryMethodToUse; argsHolderToUse.storeCache(merged, factoryMethodToUse); } } wrapper.setBeanInstance(instantiate(beanName, merged, factoryBean, factoryMethodToUse, argsToUse)); return wrapper; } private Object instantiate(String beanName, RootBeanDefinition merged, @Nullable Object factoryBean, Method factoryMethod, Object[] args) { try { return beanFactory.getInstantiationStrategy().instantiate( merged, beanName, beanFactory, factoryBean, factoryMethod, args); } catch (Throwable ex) { throw new BeanCreationException(merged.getResourceDescription(), beanName, ex.getMessage(), ex); } } /** * Resolve the constructor arguments for this bean into the resolvedValues object. * This may involve looking up other beans. *

This method is also used for handling invocations of static factory methods. */ private int resolveConstructorArguments(String beanName, BeanDefinition merged, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = customConverter != null ? customConverter : bw; var valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, merged, converter); int minNrOfArgs = cargs.getArgumentCount(); for (Map.Entry entry : cargs.getIndexedArgumentValues().entrySet()) { int index = entry.getKey(); if (index < 0) { throw new BeanCreationException(merged.getResourceDescription(), beanName, "Invalid constructor argument index: " + index); } if (index + 1 > minNrOfArgs) { minNrOfArgs = index + 1; } ValueHolder valueHolder = entry.getValue(); if (valueHolder.isConverted()) { resolvedValues.addIndexedArgumentValue(index, valueHolder); } else { Object resolvedValue = valueResolver.resolveValueIfNecessary( "constructor argument", valueHolder.getValue()); ValueHolder resolvedValueHolder = new ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName()); resolvedValueHolder.setSource(valueHolder); resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder); } } for (ValueHolder valueHolder : cargs.getGenericArgumentValues()) { if (valueHolder.isConverted()) { resolvedValues.addGenericArgumentValue(valueHolder); } else { Object resolvedValue = valueResolver.resolveValueIfNecessary( "constructor argument", valueHolder.getValue()); ValueHolder resolvedValueHolder = new ValueHolder( resolvedValue, valueHolder.getType(), valueHolder.getName()); resolvedValueHolder.setSource(valueHolder); resolvedValues.addGenericArgumentValue(resolvedValueHolder); } } return minNrOfArgs; } /** * Create an array of arguments to invoke a constructor or factory method, * given the resolved constructor argument values. */ private ArgumentsHolder createArgumentArray(String beanName, RootBeanDefinition merged, @Nullable ConstructorArgumentValues resolvedValues, Class[] paramTypes, @Nullable String[] paramNames, Executable executable, BeanWrapper wrapper, boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = customConverter != null ? customConverter : wrapper; ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); var usedValueHolders = new HashSet(paramTypes.length); var allAutowiredBeanNames = new LinkedHashSet(paramTypes.length * 2); for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class paramType = paramTypes[paramIndex]; String paramName = paramNames != null ? paramNames[paramIndex] : ""; // Try to find matching constructor argument value, either indexed or generic. ValueHolder valueHolder = null; if (resolvedValues != null) { valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders); // If we couldn't find a direct match and are not supposed to autowire, // let's try the next generic, untyped argument value as fallback: // it could match after type conversion (for example, String -> int). if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) { valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders); } } if (valueHolder != null) { // We found a potential match - let's give it a try. // Do not consider the same value definition multiple times! usedValueHolders.add(valueHolder); Object originalValue = valueHolder.getValue(); Object convertedValue; if (valueHolder.isConverted()) { convertedValue = valueHolder.getConvertedValue(); args.preparedArguments[paramIndex] = convertedValue; } else { MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); try { convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); } catch (TypeMismatchException ex) { throw new UnsatisfiedDependencyException( merged.getResourceDescription(), beanName, new InjectionPoint(methodParam), "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(valueHolder.getValue()) + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); } Object sourceHolder = valueHolder.getSource(); if (sourceHolder instanceof ValueHolder) { Object sourceValue = ((ValueHolder) sourceHolder).getValue(); args.resolveNecessary = true; args.preparedArguments[paramIndex] = sourceValue; } } args.arguments[paramIndex] = convertedValue; args.rawArguments[paramIndex] = originalValue; } else { MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex); // No explicit match found: we're either supposed to autowire or // have to fail creating an argument array for the given constructor. if (!autowiring) { throw new UnsatisfiedDependencyException( merged.getResourceDescription(), beanName, new InjectionPoint(methodParam), "Ambiguous argument values for parameter of type [" + paramType.getName() + "] - did you specify the correct bean references as arguments?"); } try { ConstructorDependencyDescriptor desc = new ConstructorDependencyDescriptor(methodParam, true); Set autowiredBeanNames = new LinkedHashSet<>(2); Object arg = resolveAutowiredArgument( desc, paramType, beanName, autowiredBeanNames, converter, fallback); if (arg != null) { setShortcutIfPossible(desc, paramType, autowiredBeanNames); } allAutowiredBeanNames.addAll(autowiredBeanNames); args.rawArguments[paramIndex] = arg; args.arguments[paramIndex] = arg; args.preparedArguments[paramIndex] = desc; args.resolveNecessary = true; } catch (BeansException ex) { throw new UnsatisfiedDependencyException( merged.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex); } } } registerDependentBeans(executable, beanName, allAutowiredBeanNames); return args; } /** * Resolve the prepared arguments stored in the given bean definition. */ private Object[] resolvePreparedArguments(String beanName, BeanDefinition merged, Executable executable, Object[] argsToResolve, BeanWrapper bw) { TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); TypeConverter converter = customConverter != null ? customConverter : bw; BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(beanFactory, beanName, merged, converter); Class[] paramTypes = executable.getParameterTypes(); Object[] resolvedArgs = new Object[argsToResolve.length]; for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { Object argValue = argsToResolve[argIndex]; Class paramType = paramTypes[argIndex]; boolean convertNecessary = false; if (argValue instanceof ConstructorDependencyDescriptor descriptor) { try { argValue = resolveAutowiredArgument(descriptor, paramType, beanName, null, converter, true); } catch (BeansException ex) { // Unexpected target bean mismatch for cached argument -> re-resolve Set autowiredBeanNames = null; if (descriptor.hasShortcut()) { // Reset shortcut and try to re-resolve it in this thread... descriptor.setShortcut(null); autowiredBeanNames = new LinkedHashSet<>(2); } log.debug("Failed to resolve cached argument", ex); argValue = resolveAutowiredArgument(descriptor, paramType, beanName, autowiredBeanNames, converter, true); if (autowiredBeanNames != null && !descriptor.hasShortcut()) { // We encountered as stale shortcut before, and the shortcut has // not been re-resolved by another thread in the meantime... if (argValue != null) { setShortcutIfPossible(descriptor, paramType, autowiredBeanNames); } registerDependentBeans(executable, beanName, autowiredBeanNames); } } } else if (argValue instanceof BeanMetadataElement) { argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); convertNecessary = true; } else if (argValue instanceof String text) { argValue = this.beanFactory.evaluateBeanDefinitionString(text, merged); convertNecessary = true; } if (convertNecessary) { MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); try { argValue = converter.convertIfNecessary(argValue, paramType, methodParam); } catch (TypeMismatchException ex) { throw new UnsatisfiedDependencyException( merged.getResourceDescription(), beanName, new InjectionPoint(methodParam), "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); } } resolvedArgs[argIndex] = argValue; } return resolvedArgs; } private Constructor getUserDeclaredConstructor(Constructor constructor) { Class declaringClass = constructor.getDeclaringClass(); Class userClass = ClassUtils.getUserClass(declaringClass); if (userClass != declaringClass) { try { return userClass.getDeclaredConstructor(constructor.getParameterTypes()); } catch (NoSuchMethodException ex) { // No equivalent constructor on user class (superclass)... // Let's proceed with the given constructor as we usually would. } } return constructor; } /** * Template method for resolving the specified argument which is supposed to be autowired. */ @Nullable Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class paramType, String beanName, @Nullable Set autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { if (InjectionPoint.class.isAssignableFrom(paramType)) { InjectionPoint injectionPoint = currentInjectionPoint.get(); if (injectionPoint == null) { throw new IllegalStateException("No current InjectionPoint available for " + descriptor); } return injectionPoint; } try { return beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); } catch (NoUniqueBeanDefinitionException ex) { throw ex; } catch (NoSuchBeanDefinitionException ex) { if (fallback) { // Single constructor or factory method -> let's return an empty array/collection // for e.g. a vararg or a non-null List/Set/Map parameter. if (paramType.isArray()) { return Array.newInstance(paramType.getComponentType(), 0); } else if (CollectionUtils.isApproximableCollectionType(paramType)) { return CollectionUtils.createCollection(paramType, 0); } else if (CollectionUtils.isApproximableMapType(paramType)) { return CollectionUtils.createMap(paramType, 0); } } throw ex; } } private void setShortcutIfPossible(ConstructorDependencyDescriptor descriptor, Class paramType, Set autowiredBeanNames) { if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, paramType)) { descriptor.setShortcut(autowiredBeanName); } } } private void registerDependentBeans(Executable executable, String beanName, Set autowiredBeanNames) { for (String autowiredBeanName : autowiredBeanNames) { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (log.isDebugEnabled()) { log.debug("Autowiring by type from bean name '{}' via {} to bean named '{}'", beanName, (executable instanceof Constructor ? "constructor" : "factory method"), autowiredBeanName); } } } // AOT-oriented pre-resolution public Executable resolveConstructorOrFactoryMethod(String beanName, RootBeanDefinition mbd) { Supplier beanType = () -> getBeanType(beanName, mbd); List valueTypes = (mbd.hasConstructorArgumentValues() ? determineParameterValueTypes(mbd) : Collections.emptyList()); Method resolvedFactoryMethod = resolveFactoryMethod(beanName, mbd, valueTypes); if (resolvedFactoryMethod != null) { return resolvedFactoryMethod; } Class factoryBeanClass = getFactoryBeanClass(beanName, mbd); if (factoryBeanClass != null && !factoryBeanClass.equals(mbd.getResolvableType().toClass())) { ResolvableType resolvableType = mbd.getResolvableType(); boolean isCompatible = ResolvableType.forClass(factoryBeanClass) .as(FactoryBean.class).getGeneric(0).isAssignableFrom(resolvableType); Assert.state(isCompatible, () -> String.format( "Incompatible target type '%s' for factory bean '%s'", resolvableType.toClass().getName(), factoryBeanClass.getName())); Executable executable = resolveConstructor(beanName, mbd, () -> ResolvableType.forClass(factoryBeanClass), valueTypes); if (executable != null) { return executable; } throw new IllegalStateException("No suitable FactoryBean constructor found for " + mbd + " and argument types " + valueTypes); } Executable resolvedConstructor = resolveConstructor(beanName, mbd, beanType, valueTypes); if (resolvedConstructor != null) { return resolvedConstructor; } throw new IllegalStateException("No constructor or factory method candidate found for " + mbd + " and argument types " + valueTypes); } private List determineParameterValueTypes(RootBeanDefinition mbd) { List parameterTypes = new ArrayList<>(); for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getIndexedArgumentValues().values()) { parameterTypes.add(determineParameterValueType(mbd, valueHolder)); } for (ValueHolder valueHolder : mbd.getConstructorArgumentValues().getGenericArgumentValues()) { parameterTypes.add(determineParameterValueType(mbd, valueHolder)); } return parameterTypes; } private ResolvableType determineParameterValueType(RootBeanDefinition mbd, ValueHolder valueHolder) { if (valueHolder.getType() != null) { return ResolvableType.forClass( ClassUtils.resolveClassName(valueHolder.getType(), this.beanFactory.getBeanClassLoader())); } Object value = valueHolder.getValue(); if (value instanceof BeanReference br) { if (value instanceof RuntimeBeanReference rbr) { if (rbr.getBeanType() != null) { return ResolvableType.forClass(rbr.getBeanType()); } } return ResolvableType.forClass(this.beanFactory.getType(br.getBeanName(), false)); } if (value instanceof BeanDefinition innerBd) { String nameToUse = "(inner bean)"; ResolvableType type = getBeanType(nameToUse, this.beanFactory.getMergedBeanDefinition(nameToUse, innerBd, mbd)); return (FactoryBean.class.isAssignableFrom(type.toClass()) ? type.as(FactoryBean.class).getGeneric(0) : type); } if (value instanceof TypedStringValue typedValue) { if (typedValue.hasTargetType()) { return ResolvableType.forClass(typedValue.getTargetType()); } return ResolvableType.forClass(String.class); } if (value instanceof Class clazz) { return ResolvableType.forClassWithGenerics(Class.class, clazz); } return ResolvableType.forInstance(value); } @Nullable private Executable resolveConstructor(String beanName, RootBeanDefinition mbd, Supplier beanType, List valueTypes) { Class type = ClassUtils.getUserClass(beanType.get().toClass()); Constructor[] ctors = beanFactory.determineConstructorsFromPostProcessors(type, beanName); if (ctors == null) { if (!mbd.hasConstructorArgumentValues()) { ctors = mbd.getPreferredConstructors(); if (ObjectUtils.isEmpty(ctors)) { ctors = new Constructor[] { BeanUtils.getConstructor(type) }; } } if (ctors == null) { ctors = (mbd.isNonPublicAccessAllowed() ? type.getDeclaredConstructors() : type.getConstructors()); } } if (ctors.length == 1) { return ctors[0]; } Function, List> parameterTypesFactory = executable -> { List types = new ArrayList<>(); for (int i = 0; i < executable.getParameterCount(); i++) { types.add(ResolvableType.forConstructorParameter(executable, i)); } return types; }; List matches = Arrays.stream(ctors) .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.NONE)) .toList(); if (matches.size() == 1) { return matches.get(0); } List assignableElementFallbackMatches = Arrays .stream(ctors) .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.ASSIGNABLE_ELEMENT)) .toList(); if (assignableElementFallbackMatches.size() == 1) { return assignableElementFallbackMatches.get(0); } List typeConversionFallbackMatches = Arrays .stream(ctors) .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.TYPE_CONVERSION)) .toList(); return (typeConversionFallbackMatches.size() == 1 ? typeConversionFallbackMatches.get(0) : null); } @Nullable private Method resolveFactoryMethod(String beanName, RootBeanDefinition mbd, List valueTypes) { if (mbd.isFactoryMethodUnique) { Method resolvedFactoryMethod = mbd.getResolvedFactoryMethod(); if (resolvedFactoryMethod != null) { return resolvedFactoryMethod; } } String factoryMethodName = mbd.getFactoryMethodName(); if (factoryMethodName != null) { String factoryBeanName = mbd.getFactoryBeanName(); Class factoryClass; boolean isStatic; if (factoryBeanName != null) { factoryClass = this.beanFactory.getType(factoryBeanName); isStatic = false; } else { factoryClass = this.beanFactory.resolveBeanClass(beanName, mbd); isStatic = true; } Assert.state(factoryClass != null, () -> "Failed to determine bean class of " + mbd); Method[] rawCandidates = getCandidateMethods(factoryClass, mbd); List candidates = new ArrayList<>(); for (Method candidate : rawCandidates) { if ((!isStatic || isStaticCandidate(candidate, factoryClass)) && mbd.isFactoryMethod(candidate)) { candidates.add(candidate); } } Method result = null; if (candidates.size() == 1) { result = candidates.get(0); } else if (candidates.size() > 1) { Function> parameterTypesFactory = method -> { List types = new ArrayList<>(); for (int i = 0; i < method.getParameterCount(); i++) { types.add(ResolvableType.forMethodParameter(method, i)); } return types; }; result = (Method) resolveFactoryMethod(candidates, parameterTypesFactory, valueTypes); } if (result == null) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "No matching factory method found on class [" + factoryClass.getName() + "]: " + (mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") + "factory method '" + mbd.getFactoryMethodName() + "'. "); } return result; } return null; } @Nullable private Executable resolveFactoryMethod(List executables, Function> parameterTypesFactory, List valueTypes) { List matches = executables.stream() .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.NONE)) .toList(); if (matches.size() == 1) { return matches.get(0); } List assignableElementFallbackMatches = executables.stream() .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.ASSIGNABLE_ELEMENT)) .toList(); if (assignableElementFallbackMatches.size() == 1) { return assignableElementFallbackMatches.get(0); } List typeConversionFallbackMatches = executables.stream() .filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.TYPE_CONVERSION)) .toList(); Assert.state(typeConversionFallbackMatches.size() <= 1, () -> "Multiple matches with parameters '" + valueTypes + "': " + typeConversionFallbackMatches); return (typeConversionFallbackMatches.size() == 1 ? typeConversionFallbackMatches.get(0) : null); } private boolean match(List parameterTypes, List valueTypes, FallbackMode fallbackMode) { if (parameterTypes.size() != valueTypes.size()) { return false; } for (int i = 0; i < parameterTypes.size(); i++) { if (!isMatch(parameterTypes.get(i), valueTypes.get(i), fallbackMode)) { return false; } } return true; } private boolean isMatch(ResolvableType parameterType, ResolvableType valueType, FallbackMode fallbackMode) { if (isAssignable(valueType).test(parameterType)) { return true; } return switch (fallbackMode) { case ASSIGNABLE_ELEMENT -> isAssignable(valueType).test(extractElementType(parameterType)); case TYPE_CONVERSION -> typeConversionFallback(valueType).test(parameterType); default -> false; }; } private Predicate isAssignable(ResolvableType valueType) { return parameterType -> valueType == ResolvableType.NONE || parameterType.isAssignableFrom(valueType); } private ResolvableType extractElementType(ResolvableType parameterType) { if (parameterType.isArray()) { return parameterType.getComponentType(); } if (Collection.class.isAssignableFrom(parameterType.toClass())) { return parameterType.as(Collection.class).getGeneric(0); } return ResolvableType.NONE; } private Predicate typeConversionFallback(ResolvableType valueType) { return parameterType -> { if (valueOrCollection(valueType, this::isStringForClassFallback).test(parameterType)) { return true; } return valueOrCollection(valueType, this::isSimpleValueType).test(parameterType); }; } private Predicate valueOrCollection(ResolvableType valueType, Function> predicateProvider) { return parameterType -> { if (predicateProvider.apply(valueType).test(parameterType)) { return true; } if (predicateProvider.apply(extractElementType(valueType)).test(extractElementType(parameterType))) { return true; } return (predicateProvider.apply(valueType).test(extractElementType(parameterType))); }; } /** * Return a {@link Predicate} for a parameter type that checks if its target * value is a {@link Class} and the value type is a {@link String}. This is * a regular use cases where a {@link Class} is defined in the bean * definition as an FQN. * * @param valueType the type of the value * @return a predicate to indicate a fallback match for a String to Class * parameter */ private Predicate isStringForClassFallback(ResolvableType valueType) { return parameterType -> (valueType.isAssignableFrom(String.class) && parameterType.isAssignableFrom(Class.class)); } private Predicate isSimpleValueType(ResolvableType valueType) { return parameterType -> (BeanUtils.isSimpleValueType(parameterType.toClass()) && BeanUtils.isSimpleValueType(valueType.toClass())); } @Nullable private Class getFactoryBeanClass(String beanName, RootBeanDefinition mbd) { Class beanClass = this.beanFactory.resolveBeanClass(beanName, mbd); return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass) ? beanClass : null); } private ResolvableType getBeanType(String beanName, RootBeanDefinition mbd) { ResolvableType resolvableType = mbd.getResolvableType(); if (resolvableType != ResolvableType.NONE) { return resolvableType; } return ResolvableType.forClass(this.beanFactory.resolveBeanClass(beanName, mbd)); } static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) { InjectionPoint old = currentInjectionPoint.get(); if (injectionPoint != null) { currentInjectionPoint.set(injectionPoint); } else { currentInjectionPoint.remove(); } return old; } /** * Private inner class for holding argument combinations. */ private static class ArgumentsHolder { public final Object[] arguments; public final Object[] rawArguments; public final Object[] preparedArguments; public boolean resolveNecessary = false; public ArgumentsHolder(int size) { this.arguments = new Object[size]; this.rawArguments = new Object[size]; this.preparedArguments = new Object[size]; } public ArgumentsHolder(Object[] args) { this.arguments = args; this.rawArguments = args; this.preparedArguments = args; } public int getTypeDifferenceWeight(Class[] paramTypes) { // If valid arguments found, determine type difference weight. // Try type difference weight on both the converted arguments and // the raw arguments. If the raw weight is better, use it. // Decrease raw weight by 1024 to prefer it over equal converted weight. int typeDiffWeight = ReflectiveMethodInvoker.getTypeDifferenceWeight(paramTypes, arguments); int rawTypeDiffWeight = ReflectiveMethodInvoker.getTypeDifferenceWeight(paramTypes, rawArguments) - 1024; return Math.min(rawTypeDiffWeight, typeDiffWeight); } public int getAssignabilityWeight(Class[] paramTypes) { for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], arguments[i])) { return Integer.MAX_VALUE; } } for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], rawArguments[i])) { return Integer.MAX_VALUE - 512; } } return Integer.MAX_VALUE - 1024; } public void storeCache(RootBeanDefinition merged, Executable constructorOrFactoryMethod) { synchronized(merged.constructorArgumentLock) { merged.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod; merged.constructorArgumentsResolved = true; if (resolveNecessary) { merged.preparedConstructorArguments = preparedArguments; } else { merged.resolvedConstructorArguments = arguments; } } } } /** * Delegate for checking Java 6's {@link ConstructorProperties} annotation. */ private static class ConstructorPropertiesChecker { @Nullable public static String[] evaluate(Constructor candidate, int paramCount) { ConstructorProperties cp = candidate.getAnnotation(ConstructorProperties.class); if (cp != null) { String[] names = cp.value(); if (names.length != paramCount) { throw new IllegalStateException("Constructor annotated with @ConstructorProperties but not " + "corresponding to actual number of parameters (" + paramCount + "): " + candidate); } return names; } else { return null; } } } /** * DependencyDescriptor marker for constructor arguments, * for differentiating between a provided DependencyDescriptor instance * and an internally built DependencyDescriptor for autowiring purposes. */ @SuppressWarnings("serial") private static class ConstructorDependencyDescriptor extends DependencyDescriptor { @Nullable private volatile String shortcut; public ConstructorDependencyDescriptor(MethodParameter methodParameter, boolean required) { super(methodParameter, required); } public void setShortcut(@Nullable String shortcut) { this.shortcut = shortcut; } public boolean hasShortcut() { return (this.shortcut != null); } @Override public Object resolveShortcut(BeanFactory beanFactory) { String shortcut = this.shortcut; return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : null); } @Override public boolean usesStandardBeanLookup() { return true; } } private enum FallbackMode { NONE, ASSIGNABLE_ELEMENT, TYPE_CONVERSION } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy