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

infra.beans.factory.aot.BeanInstanceSupplier 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.aot;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import infra.aot.hint.ExecutableMode;
import infra.beans.BeanInstantiationException;
import infra.beans.BeanUtils;
import infra.beans.BeansException;
import infra.beans.TypeConverter;
import infra.beans.factory.UnsatisfiedDependencyException;
import infra.beans.factory.config.ConstructorArgumentValues;
import infra.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import infra.beans.factory.config.DependencyDescriptor;
import infra.beans.factory.support.AbstractAutowireCapableBeanFactory;
import infra.beans.factory.support.BeanDefinitionValueResolver;
import infra.beans.factory.support.InstanceSupplier;
import infra.beans.factory.support.InstantiationStrategy;
import infra.beans.factory.support.RegisteredBean;
import infra.beans.factory.support.RootBeanDefinition;
import infra.core.MethodParameter;
import infra.lang.Assert;
import infra.lang.Nullable;
import infra.util.ClassUtils;
import infra.util.CollectionUtils;
import infra.util.ReflectionUtils;
import infra.util.function.ThrowingBiFunction;
import infra.util.function.ThrowingFunction;
import infra.util.function.ThrowingSupplier;

/**
 * Specialized {@link InstanceSupplier} that provides the factory {@link Method}
 * used to instantiate the underlying bean instance, if any. Transparently
 * handles resolution of {@link AutowiredArguments} if necessary. Typically used
 * in AOT-processed applications as a targeted alternative to the reflection
 * based injection.
 *
 * 

If no {@code generator} is provided, reflection is used to instantiate the * bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are * contributed. Multiple generator callback styles are supported: *

    *
  • A function with the {@code registeredBean} and resolved {@code arguments} * for executables that require arguments resolution. An * {@link ExecutableMode#INTROSPECT introspection} hint is added so that * parameter annotations can be read
  • *
  • A function with only the {@code registeredBean} for simpler cases that * do not require resolution of arguments
  • *
  • A supplier when a method reference can be used
  • *
* Generator callbacks handle checked exceptions so that the caller does not * have to deal with them. * * @param the type of instance supplied by this supplier * @author Phillip Webb * @author Stephane Nicoll * @author Harry Yang * @see AutowiredArguments * @since 4.0 */ public final class BeanInstanceSupplier extends AutowiredElementResolver implements InstanceSupplier { private final ExecutableLookup lookup; @Nullable private final ThrowingFunction generatorWithoutArguments; @Nullable private final ThrowingBiFunction generatorWithArguments; @Nullable private final String[] shortcutBeanNames; private BeanInstanceSupplier(ExecutableLookup lookup, @Nullable ThrowingFunction generatorWithoutArguments, @Nullable ThrowingBiFunction generatorWithArguments, @Nullable String[] shortcutBeanNames) { this.lookup = lookup; this.generatorWithoutArguments = generatorWithoutArguments; this.generatorWithArguments = generatorWithArguments; this.shortcutBeanNames = shortcutBeanNames; } /** * Create a {@link BeanInstanceSupplier} that resolves * arguments for the specified bean constructor. * * @param the type of instance supplied * @param parameterTypes the constructor parameter types * @return a new {@link BeanInstanceSupplier} instance */ public static BeanInstanceSupplier forConstructor(Class... parameterTypes) { Assert.notNull(parameterTypes, "'parameterTypes' is required"); Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); return new BeanInstanceSupplier<>(new ConstructorLookup(parameterTypes), null, null, null); } /** * Create a new {@link BeanInstanceSupplier} that * resolves arguments for the specified factory method. * * @param the type of instance supplied * @param declaringClass the class that declares the factory method * @param methodName the factory method name * @param parameterTypes the factory method parameter types * @return a new {@link BeanInstanceSupplier} instance */ public static BeanInstanceSupplier forFactoryMethod( Class declaringClass, String methodName, Class... parameterTypes) { Assert.notNull(declaringClass, "'declaringClass' is required"); Assert.hasText(methodName, "'methodName' must not be empty"); Assert.notNull(parameterTypes, "'parameterTypes' is required"); Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements"); return new BeanInstanceSupplier<>( new FactoryMethodLookup(declaringClass, methodName, parameterTypes), null, null, null); } ExecutableLookup getLookup() { return this.lookup; } /** * Return a new {@link BeanInstanceSupplier} instance that uses the specified * {@code generator} bi-function to instantiate the underlying bean. * * @param generator a {@link ThrowingBiFunction} that uses the * {@link RegisteredBean} and resolved {@link AutowiredArguments} to * instantiate the underlying bean * @return a new {@link BeanInstanceSupplier} instance with the specified generator */ public BeanInstanceSupplier withGenerator(ThrowingBiFunction generator) { Assert.notNull(generator, "'generator' is required"); return new BeanInstanceSupplier<>(this.lookup, null, generator, this.shortcutBeanNames); } /** * Return a new {@link BeanInstanceSupplier} instance that uses the specified * {@code generator} function to instantiate the underlying bean. * * @param generator a {@link ThrowingFunction} that uses the * {@link RegisteredBean} to instantiate the underlying bean * @return a new {@link BeanInstanceSupplier} instance with the specified generator */ public BeanInstanceSupplier withGenerator(ThrowingFunction generator) { Assert.notNull(generator, "'generator' is required"); return new BeanInstanceSupplier<>(this.lookup, generator, null, this.shortcutBeanNames); } /** * Return a new {@link BeanInstanceSupplier} instance that uses * direct bean name injection shortcuts for specific parameters. * * @param beanNames the bean names to use as shortcut (aligned with the * constructor or factory method parameters) * @return a new {@link BeanInstanceSupplier} instance that uses the * given shortcut bean names */ public BeanInstanceSupplier withShortcut(String... beanNames) { return new BeanInstanceSupplier<>( this.lookup, this.generatorWithoutArguments, this.generatorWithArguments, beanNames); } @SuppressWarnings("unchecked") @Override public T get(RegisteredBean registeredBean) { Assert.notNull(registeredBean, "'registeredBean' is required"); if (this.generatorWithoutArguments != null) { Executable executable = getFactoryMethodForGenerator(); return invokeBeanSupplier(executable, () -> this.generatorWithoutArguments.apply(registeredBean)); } else if (this.generatorWithArguments != null) { Executable executable = getFactoryMethodForGenerator(); AutowiredArguments arguments = resolveArguments(registeredBean, executable != null ? executable : this.lookup.get(registeredBean)); return invokeBeanSupplier(executable, () -> this.generatorWithArguments.apply(registeredBean, arguments)); } else { Executable executable = this.lookup.get(registeredBean); Object[] arguments = resolveArguments(registeredBean, executable).toArray(); return invokeBeanSupplier(executable, () -> (T) instantiate(registeredBean, executable, arguments)); } } @Override @Nullable public Method getFactoryMethod() { // Cached factory method retrieval for qualifier introspection etc. if (this.lookup instanceof FactoryMethodLookup factoryMethodLookup) { return factoryMethodLookup.get(); } return null; } @Nullable private Method getFactoryMethodForGenerator() { // Avoid unnecessary currentlyInvokedFactoryMethod exposure outside of full configuration classes. if (this.lookup instanceof FactoryMethodLookup factoryMethodLookup && factoryMethodLookup.declaringClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { return factoryMethodLookup.get(); } return null; } private T invokeBeanSupplier(@Nullable Executable executable, ThrowingSupplier beanSupplier) { if (executable instanceof Method method) { return InstantiationStrategy.instantiateWithFactoryMethod(method, beanSupplier); } return beanSupplier.get(); } /** * Resolve arguments for the specified registered bean. * * @param registeredBean the registered bean * @return the resolved constructor or factory method arguments */ AutowiredArguments resolveArguments(RegisteredBean registeredBean) { Assert.notNull(registeredBean, "'registeredBean' is required"); return resolveArguments(registeredBean, this.lookup.get(registeredBean)); } private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Executable executable) { int parameterCount = executable.getParameterCount(); Object[] resolved = new Object[parameterCount]; Assert.isTrue(this.shortcutBeanNames == null || this.shortcutBeanNames.length == resolved.length, () -> "'shortcuts' must contain " + resolved.length + " elements"); ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable); Set autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2); int startIndex = (executable instanceof Constructor constructor && ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0; for (int i = startIndex; i < parameterCount; i++) { MethodParameter parameter = getMethodParameter(executable, i); DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true); String shortcut = (this.shortcutBeanNames != null ? this.shortcutBeanNames[i] : null); if (shortcut != null) { descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut); } ValueHolder argumentValue = argumentValues[i]; resolved[i] = resolveAutowiredArgument( registeredBean, descriptor, argumentValue, autowiredBeanNames); } registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames); return AutowiredArguments.of(resolved); } private MethodParameter getMethodParameter(Executable executable, int index) { if (executable instanceof Constructor constructor) { return new MethodParameter(constructor, index); } if (executable instanceof Method method) { return new MethodParameter(method, index); } throw new IllegalStateException("Unsupported executable: " + executable.getClass().getName()); } private ValueHolder[] resolveArgumentValues(RegisteredBean registeredBean, Executable executable) { Parameter[] parameters = executable.getParameters(); ValueHolder[] resolved = new ValueHolder[parameters.length]; RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); if (beanDefinition.hasConstructorArgumentValues() && registeredBean.getBeanFactory() instanceof AbstractAutowireCapableBeanFactory beanFactory) { BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver( beanFactory, registeredBean.getBeanName(), beanDefinition, beanFactory.getTypeConverter()); ConstructorArgumentValues values = resolveConstructorArguments( valueResolver, beanDefinition.getConstructorArgumentValues()); var usedValueHolders = CollectionUtils.newHashSet(parameters.length); for (int i = 0; i < parameters.length; i++) { Class parameterType = parameters[i].getType(); String parameterName = (parameters[i].isNamePresent() ? parameters[i].getName() : null); ValueHolder valueHolder = values.getArgumentValue( i, parameterType, parameterName, usedValueHolders); if (valueHolder != null) { resolved[i] = valueHolder; usedValueHolders.add(valueHolder); } } } return resolved; } private ConstructorArgumentValues resolveConstructorArguments( BeanDefinitionValueResolver valueResolver, ConstructorArgumentValues constructorArguments) { ConstructorArgumentValues resolvedConstructorArguments = new ConstructorArgumentValues(); for (Map.Entry entry : constructorArguments.getIndexedArgumentValues().entrySet()) { resolvedConstructorArguments.addIndexedArgumentValue(entry.getKey(), resolveArgumentValue(valueResolver, entry.getValue())); } for (ConstructorArgumentValues.ValueHolder valueHolder : constructorArguments.getGenericArgumentValues()) { resolvedConstructorArguments.addGenericArgumentValue(resolveArgumentValue(valueResolver, valueHolder)); } return resolvedConstructorArguments; } private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver, ValueHolder valueHolder) { if (valueHolder.isConverted()) { return valueHolder; } Object value = resolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue()); ValueHolder resolvedHolder = new ValueHolder(value, valueHolder.getType(), valueHolder.getName()); resolvedHolder.setSource(valueHolder); return resolvedHolder; } @Nullable private Object resolveAutowiredArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor, @Nullable ValueHolder argumentValue, Set autowiredBeanNames) { TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter(); if (argumentValue != null) { return argumentValue.isConverted() ? argumentValue.getConvertedValue() : typeConverter.convertIfNecessary(argumentValue.getValue(), descriptor.getDependencyType(), descriptor.getMethodParameter()); } try { return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeanNames); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), descriptor, ex); } } private Object instantiate(RegisteredBean registeredBean, Executable executable, Object[] args) { if (executable instanceof Constructor constructor) { return BeanUtils.newInstance(constructor, args); } if (executable instanceof Method method) { Object target = null; String factoryBeanName = registeredBean.getMergedBeanDefinition().getFactoryBeanName(); if (factoryBeanName != null) { target = registeredBean.getBeanFactory().getBean(factoryBeanName, method.getDeclaringClass()); } else if (!Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException("Cannot invoke instance method without factoryBeanName: " + method); } try { ReflectionUtils.makeAccessible(method); return method.invoke(target, args); } catch (Throwable ex) { throw new BeanInstantiationException(method, ex.getMessage(), ex); } } throw new IllegalStateException("Unsupported executable " + executable.getClass().getName()); } private static String toCommaSeparatedNames(Class... parameterTypes) { return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", ")); } /** * Performs lookup of the {@link Executable}. */ static abstract class ExecutableLookup { abstract Executable get(RegisteredBean registeredBean); } /** * Performs lookup of the {@link Constructor}. */ private static class ConstructorLookup extends ExecutableLookup { private final Class[] parameterTypes; ConstructorLookup(Class[] parameterTypes) { this.parameterTypes = parameterTypes; } @Override public Executable get(RegisteredBean registeredBean) { Class beanClass = registeredBean.getMergedBeanDefinition().getBeanClass(); try { return beanClass.getDeclaredConstructor(this.parameterTypes); } catch (NoSuchMethodException ex) { throw new IllegalArgumentException( "%s cannot be found on %s".formatted(this, beanClass.getName()), ex); } } @Override public String toString() { return "Constructor with parameter types [%s]".formatted(toCommaSeparatedNames(this.parameterTypes)); } } /** * Performs lookup of the factory {@link Method}. */ private static class FactoryMethodLookup extends ExecutableLookup { private final Class declaringClass; private final String methodName; private final Class[] parameterTypes; @Nullable private volatile Method resolvedMethod; FactoryMethodLookup(Class declaringClass, String methodName, Class[] parameterTypes) { this.declaringClass = declaringClass; this.methodName = methodName; this.parameterTypes = parameterTypes; } @Override public Executable get(RegisteredBean registeredBean) { return get(); } Method get() { Method method = this.resolvedMethod; if (method == null) { method = ReflectionUtils.findMethod( ClassUtils.getUserClass(this.declaringClass), this.methodName, this.parameterTypes); Assert.notNull(method, () -> "%s cannot be found".formatted(this)); this.resolvedMethod = method; } return method; } @Override public String toString() { return "Factory method '%s' with parameter types [%s] declared on %s".formatted( this.methodName, toCommaSeparatedNames(this.parameterTypes), this.declaringClass); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy