Add support for InjectionPoint with AOT

This commit reviews BeanInstanceSupplier to reuse more code from
ConstructorResolver. Previously, the autowired argument resolution was
partially duplicated and this commit introduces a new common path via
RegisteredBean#resolveAutowiredArgument.

Closes gh-30401
This commit is contained in:
Stephane Nicoll 2023-05-05 15:49:40 +02:00
parent a133aae8d6
commit c1f6d7197b
5 changed files with 82 additions and 34 deletions

View File

@ -16,7 +16,6 @@
package org.springframework.beans.factory.aot; package org.springframework.beans.factory.aot;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -31,8 +30,6 @@ import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory; 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.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues;
@ -44,7 +41,6 @@ import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy; import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -239,7 +235,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
return resolveArguments(registeredBean, this.lookup.get(registeredBean)); return resolveArguments(registeredBean, this.lookup.get(registeredBean));
} }
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,Executable executable) { private AutowiredArguments resolveArguments(RegisteredBean registeredBean, Executable executable) {
Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory()); Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory());
String beanName = registeredBean.getBeanName(); String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass(); Class<?> beanClass = registeredBean.getBeanClass();
@ -264,8 +260,8 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
dependencyDescriptor, shortcut, beanClass); dependencyDescriptor, shortcut, beanClass);
} }
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null); ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
resolved[i - startIndex] = resolveArgument(beanFactory, beanName, resolved[i - startIndex] = resolveArgument(registeredBean,autowiredBeans,
autowiredBeans, parameter, dependencyDescriptor, argumentValue); dependencyDescriptor, argumentValue);
} }
registerDependentBeans(beanFactory, beanName, autowiredBeans); registerDependentBeans(beanFactory, beanName, autowiredBeans);
return AutowiredArguments.of(resolved); return AutowiredArguments.of(resolved);
@ -311,36 +307,21 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
} }
@Nullable @Nullable
private Object resolveArgument(AbstractAutowireCapableBeanFactory beanFactory, private Object resolveArgument(RegisteredBean registeredBean, Set<String> autowiredBeans,
String beanName, Set<String> autowiredBeans, MethodParameter parameter,
DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) { DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) {
TypeConverter typeConverter = beanFactory.getTypeConverter(); TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();
Class<?> parameterType = parameter.getParameterType(); Class<?> parameterType = dependencyDescriptor.getMethodParameter().getParameterType();
if (argumentValue != null) { if (argumentValue != null) {
return (!argumentValue.isConverted()) ? return (!argumentValue.isConverted()) ?
typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) : typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) :
argumentValue.getConvertedValue(); argumentValue.getConvertedValue();
} }
try { try {
try { return registeredBean.resolveAutowiredArgument(dependencyDescriptor, typeConverter, autowiredBeans);
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) { catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(parameter), ex); throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), dependencyDescriptor, ex);
} }
} }

View File

@ -788,8 +788,8 @@ class ConstructorResolver {
"] - did you specify the correct bean references as arguments?"); "] - did you specify the correct bean references as arguments?");
} }
try { try {
Object autowiredArgument = resolveAutowiredArgument( Object autowiredArgument = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true),
methodParam, beanName, autowiredBeanNames, converter, fallback); beanName, autowiredBeanNames, converter, fallback);
args.rawArguments[paramIndex] = autowiredArgument; args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument;
args.preparedArguments[paramIndex] = autowiredArgumentMarker; args.preparedArguments[paramIndex] = autowiredArgumentMarker;
@ -831,7 +831,8 @@ class ConstructorResolver {
Object argValue = argsToResolve[argIndex]; Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex); MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
if (argValue == autowiredArgumentMarker) { if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true); argValue = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true),
beanName, null, converter, true);
} }
else if (argValue instanceof BeanMetadataElement) { else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
@ -872,20 +873,20 @@ class ConstructorResolver {
* Template method for resolving the specified argument which is supposed to be autowired. * Template method for resolving the specified argument which is supposed to be autowired.
*/ */
@Nullable @Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName, protected Object resolveAutowiredArgument(DependencyDescriptor descriptor, String beanName,
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
Class<?> paramType = param.getParameterType(); Class<?> paramType = descriptor.getMethodParameter().getParameterType();
if (InjectionPoint.class.isAssignableFrom(paramType)) { if (InjectionPoint.class.isAssignableFrom(paramType)) {
InjectionPoint injectionPoint = currentInjectionPoint.get(); InjectionPoint injectionPoint = currentInjectionPoint.get();
if (injectionPoint == null) { if (injectionPoint == null) {
throw new IllegalStateException("No current InjectionPoint available for " + param); throw new IllegalStateException("No current InjectionPoint available for " + descriptor);
} }
return injectionPoint; return injectionPoint;
} }
try { try {
return this.beanFactory.resolveDependency( return this.beanFactory.resolveDependency(
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); descriptor, beanName, autowiredBeanNames, typeConverter);
} }
catch (NoUniqueBeanDefinitionException ex) { catch (NoUniqueBeanDefinitionException ex) {
throw ex; throw ex;

View File

@ -17,13 +17,16 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -209,6 +212,13 @@ public final class RegisteredBean {
.resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition()); .resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition());
} }
@Nullable
public Object resolveAutowiredArgument(DependencyDescriptor descriptor, TypeConverter typeConverter,
Set<String> autowiredBeans) {
return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory())
.resolveAutowiredArgument(descriptor, getBeanName(), autowiredBeans, typeConverter, true);
}
@Override @Override
public String toString() { public String toString() {

View File

@ -59,6 +59,7 @@ import org.springframework.context.testfixture.context.annotation.CglibConfigura
import org.springframework.context.testfixture.context.annotation.ConfigurableCglibConfiguration; import org.springframework.context.testfixture.context.annotation.ConfigurableCglibConfiguration;
import org.springframework.context.testfixture.context.annotation.GenericTemplateConfiguration; import org.springframework.context.testfixture.context.annotation.GenericTemplateConfiguration;
import org.springframework.context.testfixture.context.annotation.InitDestroyComponent; import org.springframework.context.testfixture.context.annotation.InitDestroyComponent;
import org.springframework.context.testfixture.context.annotation.InjectionPointConfiguration;
import org.springframework.context.testfixture.context.annotation.LazyAutowiredFieldComponent; import org.springframework.context.testfixture.context.annotation.LazyAutowiredFieldComponent;
import org.springframework.context.testfixture.context.annotation.LazyAutowiredMethodComponent; import org.springframework.context.testfixture.context.annotation.LazyAutowiredMethodComponent;
import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent; import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent;
@ -315,6 +316,17 @@ class ApplicationContextAotGeneratorTests {
}); });
} }
@Test
void processAheadOfTimeWithInjectionPoint() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(InjectionPointConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBean("classToString"))
.isEqualTo(InjectionPointConfiguration.class.getName());
});
}
@Nested @Nested
@CompileWithForkedClassLoader @CompileWithForkedClassLoader
class ConfigurationClassCglibProxy { class ConfigurationClassCglibProxy {

View File

@ -0,0 +1,44 @@
/*
* Copyright 2002-2023 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.context.testfixture.context.annotation;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration(proxyBeanMethods = false)
public class InjectionPointConfiguration {
@Bean
public String classToString(Class<?> callingClass) {
return callingClass.getName();
}
@Configuration(proxyBeanMethods = false)
public static class BeansConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Class<?> callingClass(InjectionPoint injectionPoint) {
return injectionPoint.getMember().getDeclaringClass();
}
}
}