Add programmatic autowiring support classes

Add resolver utilities that can be used to perform programmatic
autowiring of fields, methods, constructors and factory methods.

The resolvers are designed to work in an AOT environment and
allows the actual injection to be performed using functional
interfaces. This allows leaner images to be created since
`introspection` hints are required rather than full `invocation`
hints.

The resolvers also provide a reflection based fallback that can
used when the functional interface cannot work. For example, a
reflection based solution is required for private fields, methods
and constructors.

See gh-28414
This commit is contained in:
Phillip Webb
2022-04-13 17:27:05 -07:00
parent 3209d7f126
commit f2cf78c525
8 changed files with 2354 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Resolved arguments to be autowired.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @see AutowiredInstantiationArgumentsResolver
* @see AutowiredMethodArgumentsResolver
*/
@FunctionalInterface
public interface AutowiredArguments {
/**
* Return the resolved argument at the specified index.
* @param <T> the type of the argument
* @param index the argument index
* @param requiredType the required argument type
* @return the argument
*/
@Nullable
@SuppressWarnings("unchecked")
default <T> T get(int index, Class<T> requiredType) {
Object value = get(index);
Assert.isInstanceOf(requiredType, value);
return (T) value;
}
/**
* Return the resolved argument at the specified index.
* @param <T> the type of the argument
* @param index the argument index
* @return the argument
*/
@Nullable
@SuppressWarnings("unchecked")
default <T> T get(int index) {
return (T) toArray()[index];
}
/**
* Return the resolved argument at the specified index.
* @param index the argument index
* @return the argument
*/
default Object getObject(int index) {
return toArray()[index];
}
/**
* Return the arguments as an object array.
* @return the arguments as an object array
*/
Object[] toArray();
/**
* Factory method to create a new {@link AutowiredArguments} instance from
* the given object array.
* @param arguments the arguments
* @return a new {@link AutowiredArguments} instance
*/
static AutowiredArguments of(Object[] arguments) {
Assert.notNull(arguments, "Arguments must not be null");
return () -> arguments;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import java.util.Set;
import javax.lang.model.element.Element;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.log.LogMessage;
/**
* Base class for resolvers that support autowiring related to an
* {@link Element}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
abstract class AutowiredElementResolver {
private final Log logger = LogFactory.getLog(getClass());
protected final void registerDependentBeans(ConfigurableBeanFactory beanFactory,
String beanName, Set<String> autowiredBeanNames) {
for (String autowiredBeanName : autowiredBeanNames) {
if (beanFactory.containsBean(autowiredBeanName)) {
beanFactory.registerDependentBean(autowiredBeanName, beanName);
}
logger.trace(LogMessage.format(
"Autowiring by type from bean name %s' to bean named '%s'", beanName,
autowiredBeanName));
}
}
/**
* {@link DependencyDescriptor} that supports shortcut bean resolution.
*/
@SuppressWarnings("serial")
static class ShortcutDependencyDescriptor extends DependencyDescriptor {
private final String shortcut;
private final Class<?> requiredType;
public ShortcutDependencyDescriptor(DependencyDescriptor original,
String shortcut, Class<?> requiredType) {
super(original);
this.shortcut = shortcut;
this.requiredType = requiredType;
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
return beanFactory.getBean(this.shortcut, this.requiredType);
}
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingConsumer;
/**
* Resolver used to support the autowiring of fields. Typically used in
* AOT-processed applications as a targeted alternative to the
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
* AutowiredAnnotationBeanPostProcessor}.
* <p>
* When resolving arguments in a native image, the {@link Field} being used must
* be marked with an {@link ExecutableMode#INTROSPECT introspection} hint so
* that field annotations can be read. Full {@link ExecutableMode#INVOKE
* invocation} hints are only required if the
* {@link #resolveAndSet(RegisteredBean, Object)} method of this class is being
* used (typically to support private fields).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
public final class AutowiredFieldValueResolver extends AutowiredElementResolver {
private final String fieldName;
private final boolean required;
@Nullable
private final String shortcut;
private AutowiredFieldValueResolver(String fieldName, boolean required,
@Nullable String shortcut) {
Assert.hasText(fieldName, "FieldName must not be empty");
this.fieldName = fieldName;
this.required = required;
this.shortcut = shortcut;
}
/**
* Create a new {@link AutowiredFieldValueResolver} for the specified field
* where injection is optional.
* @param fieldName the field name
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredFieldValueResolver forField(String fieldName) {
return new AutowiredFieldValueResolver(fieldName, false, null);
}
/**
* Create a new {@link AutowiredFieldValueResolver} for the specified field
* where injection is required.
* @param fieldName the field name
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredFieldValueResolver forRequiredField(String fieldName) {
return new AutowiredFieldValueResolver(fieldName, true, null);
}
/**
* Return a new {@link AutowiredFieldValueResolver} instance that uses a
* direct bean name injection shortcut.
* @param beanName the bean name to use as a shortcut
* @return a new {@link AutowiredFieldValueResolver} instance that uses the
* shortcuts
*/
public AutowiredFieldValueResolver withShortcut(String beanName) {
return new AutowiredFieldValueResolver(this.fieldName, this.required, beanName);
}
/**
* Resolve the field for the specified registered bean and provide it to the
* given action.
* @param registeredBean the registered bean
* @param action the action to execute with the resolved field value
*/
public <T> void resolve(RegisteredBean registeredBean, ThrowingConsumer<T> action) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(action, "Action must not be null");
T resolved = resolve(registeredBean);
if (resolved != null) {
action.accept(resolved);
}
}
/**
* Resolve the field value for the specified registered bean.
* @param registeredBean the registered bean
* @param requiredType the required type
* @return the resolved field value
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T resolve(RegisteredBean registeredBean, Class<T> requiredType) {
Object value = resolveObject(registeredBean);
Assert.isInstanceOf(requiredType, value);
return (T) value;
}
/**
* Resolve the field value for the specified registered bean.
* @param registeredBean the registered bean
* @return the resolved field value
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T resolve(RegisteredBean registeredBean) {
return (T) resolveObject(registeredBean);
}
/**
* Resolve the field value for the specified registered bean.
* @param registeredBean the registered bean
* @return the resolved field value
*/
@Nullable
public Object resolveObject(RegisteredBean registeredBean) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
return resolveValue(registeredBean, getField(registeredBean));
}
/**
* Resolve the field value for the specified registered bean and set it
* using reflection.
* @param registeredBean the registered bean
* @param instance the bean instance
*/
public void resolveAndSet(RegisteredBean registeredBean, Object instance) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(instance, "Instance must not be null");
Field field = getField(registeredBean);
Object resolved = resolveValue(registeredBean, field);
if (resolved != null) {
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, instance, resolved);
}
}
@Nullable
private Object resolveValue(RegisteredBean registeredBean, Field field) {
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
ConfigurableBeanFactory beanFactory = registeredBean.getBeanFactory();
DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
descriptor.setContainingClass(beanClass);
if (this.shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, this.shortcut,
field.getType());
}
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
Assert.isInstanceOf(AutowireCapableBeanFactory.class, beanFactory);
Object value = ((AutowireCapableBeanFactory) beanFactory).resolveDependency(
descriptor, beanName, autowiredBeanNames, typeConverter);
registerDependentBeans(beanFactory, beanName, autowiredBeanNames);
return value;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName,
new InjectionPoint(field), ex);
}
}
private Field getField(RegisteredBean registeredBean) {
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(),
this.fieldName);
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on "
+ registeredBean.getBeanClass().getName());
return field;
}
}

View File

@@ -0,0 +1,473 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
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.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingFunction;
/**
* Resolver used to support the autowiring of constructors or factory methods.
* Typically used in AOT-processed applications as a targeted alternative to the
* reflection based injection.
* <p>
* When resolving arguments in a native image, the {@link Constructor} or
* {@link Method} being used must be marked with an
* {@link ExecutableMode#INTROSPECT introspection} hint so that parameter
* annotations can be read. Full {@link ExecutableMode#INVOKE invocation} hints
* are only required if the {@code resolveAndInstantiate} methods of this class
* are being used (typically to support private constructors, methods or
* classes).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
* @see AutowiredArguments
*/
public final class AutowiredInstantiationArgumentsResolver
extends AutowiredElementResolver {
private final ExecutableLookup lookup;
@Nullable
private final String[] shortcuts;
private AutowiredInstantiationArgumentsResolver(ExecutableLookup lookup,
@Nullable String[] shortcuts) {
this.lookup = lookup;
this.shortcuts = shortcuts;
}
/**
* Create a {@link AutowiredInstantiationArgumentsResolver} that resolves
* arguments for the specified bean constructor.
* @param parameterTypes the constructor parameter types
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance
*/
public static AutowiredInstantiationArgumentsResolver forConstructor(
Class<?>... parameterTypes) {
Assert.notNull(parameterTypes, "ParameterTypes must not be null");
Assert.noNullElements(parameterTypes,
"ParameterTypes must not contain null elements");
return new AutowiredInstantiationArgumentsResolver(
new ConstructorLookup(parameterTypes), null);
}
/**
* Create a new {@link AutowiredInstantiationArgumentsResolver} that
* resolves arguments for the specified factory method.
* @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 AutowiredInstantiationArgumentsResolver} instance
*/
public static AutowiredInstantiationArgumentsResolver forFactoryMethod(
Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
Assert.notNull(declaringClass, "DeclaringClass must not be null");
Assert.hasText(methodName, "MethodName must not be empty");
Assert.notNull(parameterTypes, "ParameterTypes must not be null");
Assert.noNullElements(parameterTypes,
"ParameterTypes must not contain null elements");
return new AutowiredInstantiationArgumentsResolver(
new FactoryMethodLookup(declaringClass, methodName, parameterTypes),
null);
}
ExecutableLookup getLookup() {
return this.lookup;
}
/**
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance
* that uses direct bean name injection shortcuts for specific parameters.
* @param beanNames the bean names to use as shortcuts (aligned with the
* constructor or factory method parameters)
* @return a new {@link AutowiredInstantiationArgumentsResolver} instance
* that uses the shortcuts
*/
public AutowiredInstantiationArgumentsResolver withShortcuts(String... beanNames) {
return new AutowiredInstantiationArgumentsResolver(this.lookup, beanNames);
}
/**
* Resolve arguments for the specified registered bean and provide them to
* the given generator in order to return a result.
* @param registeredBean the registered bean
* @param generator the generator to execute with the resolved constructor
* or factory method arguments
*/
public <T> T resolve(RegisteredBean registeredBean,
ThrowingFunction<AutowiredArguments, T> generator) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(generator, "Action must not be null");
AutowiredArguments resolved = resolveArguments(registeredBean,
this.lookup.get(registeredBean));
return generator.apply(resolved);
}
/**
* Resolve arguments for the specified registered bean.
* @param registeredBean the registered bean
* @return the resolved constructor or factory method arguments
*/
public AutowiredArguments resolve(RegisteredBean registeredBean) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
return resolveArguments(registeredBean, this.lookup.get(registeredBean));
}
/**
* Resolve arguments for the specified registered bean and instantiate a new
* instance using reflection.
* @param registeredBean the registered bean
* @return an instance of the bean
*/
@SuppressWarnings("unchecked")
public <T> T resolveAndInstantiate(RegisteredBean registeredBean) {
return (T) resolveAndInstantiate(registeredBean, Object.class);
}
/**
* Resolve arguments for the specified registered bean and instantiate a new
* instance using reflection.
* @param registeredBean the registered bean
* @param requiredType the required result type
* @return an instance of the bean
*/
@SuppressWarnings("unchecked")
public <T> T resolveAndInstantiate(RegisteredBean registeredBean,
Class<T> requiredType) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(registeredBean, "RequiredType must not be null");
Executable executable = this.lookup.get(registeredBean);
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
Object instance = instantiate(registeredBean.getBeanFactory(), executable,
arguments.toArray());
Assert.isInstanceOf(requiredType, instance);
return (T) instance;
}
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
Executable executable) {
Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class,
registeredBean.getBeanFactory());
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) registeredBean
.getBeanFactory();
RootBeanDefinition mergedBeanDefinition = registeredBean
.getMergedBeanDefinition();
int startIndex = (executable instanceof Constructor<?> constructor
&& ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0;
int parameterCount = executable.getParameterCount();
Object[] resolved = new Object[parameterCount - startIndex];
Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length,
() -> "'shortcuts' must contain " + resolved.length + " elements");
Set<String> autowiredBeans = new LinkedHashSet<>(resolved.length);
ConstructorArgumentValues argumentValues = resolveArgumentValues(beanFactory,
beanName, mergedBeanDefinition);
for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
parameter, true);
String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex]
: null;
if (shortcut != null) {
dependencyDescriptor = new ShortcutDependencyDescriptor(
dependencyDescriptor, shortcut, beanClass);
}
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
resolved[i - startIndex] = resolveArgument(beanFactory, beanName,
autowiredBeans, parameter, dependencyDescriptor, argumentValue);
}
registerDependentBeans(beanFactory, beanName, autowiredBeans);
if (executable instanceof Method method) {
mergedBeanDefinition.setResolvedFactoryMethod(method);
}
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 ConstructorArgumentValues resolveArgumentValues(
AbstractAutowireCapableBeanFactory beanFactory, String beanName,
RootBeanDefinition mergedBeanDefinition) {
ConstructorArgumentValues resolved = new ConstructorArgumentValues();
if (mergedBeanDefinition.hasConstructorArgumentValues()) {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
beanFactory, beanName, mergedBeanDefinition,
beanFactory.getTypeConverter());
ConstructorArgumentValues values = mergedBeanDefinition
.getConstructorArgumentValues();
values.getIndexedArgumentValues().forEach((index, valueHolder) -> {
ValueHolder resolvedValue = resolveArgumentValue(valueResolver,
valueHolder);
resolved.addIndexedArgumentValue(index, resolvedValue);
});
}
return resolved;
}
private ValueHolder resolveArgumentValue(BeanDefinitionValueResolver resolver,
ValueHolder valueHolder) {
if (valueHolder.isConverted()) {
return valueHolder;
}
Object resolvedValue = resolver.resolveValueIfNecessary("constructor argument",
valueHolder.getValue());
ValueHolder resolvedValueHolder = new ValueHolder(resolvedValue,
valueHolder.getType(), valueHolder.getName());
resolvedValueHolder.setSource(valueHolder);
return resolvedValueHolder;
}
@Nullable
private Object resolveArgument(AbstractAutowireCapableBeanFactory beanFactory,
String beanName, Set<String> autowiredBeans, MethodParameter parameter,
DependencyDescriptor dependencyDescriptor,
@Nullable ValueHolder argumentValue) {
TypeConverter typeConverter = beanFactory.getTypeConverter();
Class<?> parameterType = parameter.getParameterType();
if (argumentValue != null) {
return (!argumentValue.isConverted()) ? typeConverter
.convertIfNecessary(argumentValue.getValue(), parameterType)
: argumentValue.getConvertedValue();
}
try {
try {
return beanFactory.resolveDependency(dependencyDescriptor, beanName,
autowiredBeans, typeConverter);
}
catch (NoSuchBeanDefinitionException ex) {
if (parameterType.isArray()) {
return Array.newInstance(parameterType.getComponentType(), 0);
}
if (CollectionFactory.isApproximableCollectionType(parameterType)) {
return CollectionFactory.createCollection(parameterType, 0);
}
if (CollectionFactory.isApproximableMapType(parameterType)) {
return CollectionFactory.createMap(parameterType, 0);
}
throw ex;
}
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName,
new InjectionPoint(parameter), ex);
}
}
private Object instantiate(ConfigurableBeanFactory beanFactory, Executable executable,
Object[] arguments) {
try {
if (executable instanceof Constructor<?> constructor) {
return instantiate(constructor, arguments);
}
if (executable instanceof Method method) {
return instantiate(beanFactory, method, arguments);
}
}
catch (Exception ex) {
throw new BeanCreationException(
"Unable to instantiate bean using " + executable, ex);
}
throw new IllegalStateException(
"Unsupported executable " + executable.getClass().getName());
}
private Object instantiate(Constructor<?> constructor, Object[] arguments)
throws Exception {
Class<?> declaringClass = constructor.getDeclaringClass();
if (ClassUtils.isInnerClass(declaringClass)) {
Object enclosingInstance = createInstance(declaringClass.getEnclosingClass());
arguments = ObjectUtils.addObjectToArray(arguments, enclosingInstance, 0);
}
ReflectionUtils.makeAccessible(constructor);
return constructor.newInstance(arguments);
}
private Object instantiate(ConfigurableBeanFactory beanFactory, Method method,
Object[] arguments) {
ReflectionUtils.makeAccessible(method);
Object target = getFactoryMethodTarget(beanFactory, method);
return ReflectionUtils.invokeMethod(method, target, arguments);
}
@Nullable
private Object getFactoryMethodTarget(BeanFactory beanFactory, Method method) {
if (Modifier.isStatic(method.getModifiers())) {
return null;
}
Class<?> declaringClass = method.getDeclaringClass();
return beanFactory.getBean(declaringClass);
}
private Object createInstance(Class<?> clazz) throws Exception {
if (!ClassUtils.isInnerClass(clazz)) {
Constructor<?> constructor = clazz.getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor);
return constructor.newInstance();
}
Class<?> enclosingClass = clazz.getEnclosingClass();
Constructor<?> constructor = clazz.getDeclaredConstructor(enclosingClass);
return constructor.newInstance(createInstance(enclosingClass));
}
/**
* Performs lookup of the {@link Executable}.
*/
static abstract class ExecutableLookup {
abstract Executable get(RegisteredBean registeredBean);
final String toCommaSeparatedNames(Class<?>... parameterTypes) {
return Arrays.stream(parameterTypes).map(Class::getName)
.collect(Collectors.joining(", "));
}
}
/**
* 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.getBeanClass();
try {
Class<?>[] actualParameterTypes = (!ClassUtils.isInnerClass(beanClass))
? this.parameterTypes : ObjectUtils.addObjectToArray(
this.parameterTypes, beanClass.getEnclosingClass(), 0);
return beanClass.getDeclaredConstructor(actualParameterTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalArgumentException(String.format(
"%s cannot be found on %s", this, beanClass.getName()), ex);
}
}
@Override
public String toString() {
return String.format("Constructor with parameter types [%s]",
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;
FactoryMethodLookup(Class<?> declaringClass, String methodName,
Class<?>[] parameterTypes) {
this.declaringClass = declaringClass;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}
@Override
public Executable get(RegisteredBean registeredBean) {
Method method = ReflectionUtils.findMethod(this.declaringClass,
this.methodName, this.parameterTypes);
Assert.notNull(method, () -> String.format("%s cannot be found", this));
return method;
}
@Override
public String toString() {
return String.format(
"Factory method '%s' with parameter types [%s] declared on %s",
this.methodName, toCommaSeparatedNames(this.parameterTypes),
this.declaringClass);
}
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.ThrowingConsumer;
/**
* Resolver used to support the autowiring of methods. Typically used in
* AOT-processed applications as a targeted alternative to the
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
* AutowiredAnnotationBeanPostProcessor}.
* <p>
* When resolving arguments in a native image, the {@link Method} being used
* must be marked with an {@link ExecutableMode#INTROSPECT introspection} hint
* so that field annotations can be read. Full {@link ExecutableMode#INVOKE
* invocation} hints are only required if the
* {@link #resolveAndInvoke(RegisteredBean, Object)} method of this class is
* being used (typically to support private methods).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 6.0
*/
public final class AutowiredMethodArgumentsResolver extends AutowiredElementResolver {
private final String methodName;
private final Class<?>[] parameterTypes;
private final boolean required;
@Nullable
private final String[] shortcuts;
private AutowiredMethodArgumentsResolver(String methodName, Class<?>[] parameterTypes,
boolean required, @Nullable String[] shortcuts) {
Assert.hasText(methodName, "MethodName must not be empty");
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.required = required;
this.shortcuts = shortcuts;
}
/**
* Create a new {@link AutowiredMethodArgumentsResolver} for the specified
* method where injection is optional.
* @param methodName the method name
* @param parameterTypes the factory method parameter types
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredMethodArgumentsResolver forMethod(String methodName,
Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, false,
null);
}
/**
* Create a new {@link AutowiredMethodArgumentsResolver} for the specified
* method where injection is required.
* @param methodName the method name
* @param parameterTypes the factory method parameter types
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodName,
Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, true,
null);
}
/**
* Return a new {@link AutowiredInstantiationArgumentsResolver} instance
* that uses direct bean name injection shortcuts for specific parameters.
* @param beanNames the bean names to use as shortcuts (aligned with the
* method parameters)
* @return a new {@link AutowiredMethodArgumentsResolver} instance that uses
* the shortcuts
*/
public AutowiredMethodArgumentsResolver withShortcut(String... beanNames) {
return new AutowiredMethodArgumentsResolver(this.methodName, this.parameterTypes,
this.required, beanNames);
}
/**
* Resolve the method arguments for the specified registered bean and
* provide it to the given action.
* @param registeredBean the registered bean
* @param action the action to execute with the resolved method arguments
*/
public void resolve(RegisteredBean registeredBean,
ThrowingConsumer<AutowiredArguments> action) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(action, "Action must not be null");
AutowiredArguments resolved = resolve(registeredBean);
if (resolved != null) {
action.accept(resolved);
}
}
/**
* Resolve the method arguments for the specified registered bean.
* @param registeredBean the registered bean
* @return the resolved method arguments
*/
@Nullable
public AutowiredArguments resolve(RegisteredBean registeredBean) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
return resolveArguments(registeredBean, getMethod(registeredBean));
}
/**
* Resolve the method arguments for the specified registered bean and invoke
* the method using reflection.
* @param registeredBean the registered bean
* @param instance the bean instance
*/
public void resolveAndInvoke(RegisteredBean registeredBean, Object instance) {
Assert.notNull(registeredBean, "RegisteredBean must not be null");
Assert.notNull(instance, "Instance must not be null");
Method method = getMethod(registeredBean);
AutowiredArguments resolved = resolveArguments(registeredBean, method);
if (resolved != null) {
ReflectionUtils.makeAccessible(method);
ReflectionUtils.invokeMethod(method, instance, resolved.toArray());
}
}
@Nullable
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
Method method) {
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
ConfigurableBeanFactory beanFactory = registeredBean.getBeanFactory();
Assert.isInstanceOf(AutowireCapableBeanFactory.class, beanFactory);
AutowireCapableBeanFactory autowireCapableBeanFactory = (AutowireCapableBeanFactory) beanFactory;
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
Set<String> autowiredBeanNames = new LinkedHashSet<>(argumentCount);
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < argumentCount; i++) {
MethodParameter parameter = new MethodParameter(method, i);
DependencyDescriptor descriptor = new DependencyDescriptor(parameter,
this.required);
descriptor.setContainingClass(beanClass);
String shortcut = (this.shortcuts != null) ? this.shortcuts[i] : null;
if (shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut,
parameter.getParameterType());
}
try {
Object argument = autowireCapableBeanFactory.resolveDependency(descriptor,
beanName, autowiredBeanNames, typeConverter);
if (argument == null && !this.required) {
return null;
}
arguments[i] = argument;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName,
new InjectionPoint(parameter), ex);
}
}
registerDependentBeans(beanFactory, beanName, autowiredBeanNames);
return AutowiredArguments.of(arguments);
}
private Method getMethod(RegisteredBean registeredBean) {
Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(),
this.methodName, this.parameterTypes);
Assert.notNull(method,
() -> String.format(
"Method '%s' with parameter types [%s] declared on %s",
this.methodName, toCommaSeparatedNames(this.parameterTypes),
registeredBean.getBeanClass().getName()));
return method;
}
private String toCommaSeparatedNames(Class<?>... parameterTypes) {
return Arrays.stream(parameterTypes).map(Class::getName)
.collect(Collectors.joining(", "));
}
}