Detect target of factory method with AOT

Previously, if a factory method is defined on a parent, the generated
code would blindly use the method's declaring class for both the target
of the generated code, and the signature of the method.

This commit improves the resolution by considering the factory metadata
in the BeanDefinition.

Closes gh-32609
This commit is contained in:
Stéphane Nicoll
2024-04-22 09:45:12 +02:00
parent f45e7b9b9b
commit 8a8c8fe00e
12 changed files with 214 additions and 63 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.beans.factory.aot; package org.springframework.beans.factory.aot;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -35,6 +34,7 @@ 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.support.InstanceSupplier; 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.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName; import org.springframework.javapoet.ClassName;
@@ -62,7 +62,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory; private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory;
private final Supplier<Executable> constructorOrFactoryMethod; private final Supplier<InstantiationDescriptor> instantiationDescriptor;
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode, DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
@@ -72,7 +72,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
this.beanRegistrationsCode = beanRegistrationsCode; this.beanRegistrationsCode = beanRegistrationsCode;
this.registeredBean = registeredBean; this.registeredBean = registeredBean;
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory; this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
this.constructorOrFactoryMethod = SingletonSupplier.of(registeredBean::resolveConstructorOrFactoryMethod); this.instantiationDescriptor = SingletonSupplier.of(registeredBean::resolveInstantiationDescriptor);
} }
@@ -82,7 +82,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
throw new IllegalStateException("Default code generation is not supported for bean definitions " throw new IllegalStateException("Default code generation is not supported for bean definitions "
+ "declaring an instance supplier callback: " + registeredBean.getMergedBeanDefinition()); + "declaring an instance supplier callback: " + registeredBean.getMergedBeanDefinition());
} }
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), this.constructorOrFactoryMethod.get()); Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) { while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
RegisteredBean parent = registeredBean.getParent(); RegisteredBean parent = registeredBean.getParent();
Assert.state(parent != null, "No parent available for inner bean"); Assert.state(parent != null, "No parent available for inner bean");
@@ -91,14 +91,14 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
return (target.isArray() ? ClassName.get(target.getComponentType()) : ClassName.get(target)); return (target.isArray() ? ClassName.get(target.getComponentType()) : ClassName.get(target));
} }
private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) { private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass()); Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
if (executable instanceof Constructor<?> if (instantiationDescriptor.executable() instanceof Constructor<?>
&& AccessControl.forMember(executable).isPublic() && AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
&& FactoryBean.class.isAssignableFrom(declaringClass)) { && FactoryBean.class.isAssignableFrom(declaringClass)) {
return extractTargetClassFromFactoryBean(declaringClass, beanType); return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
} }
return executable.getDeclaringClass(); return declaringClass;
} }
/** /**
@@ -238,9 +238,9 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
throw new IllegalStateException("Default code generation is not supported for bean definitions declaring " throw new IllegalStateException("Default code generation is not supported for bean definitions declaring "
+ "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition()); + "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
} }
return new InstanceSupplierCodeGenerator(generationContext, return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(),
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut) beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode(
.generateCode(this.registeredBean, this.constructorOrFactoryMethod.get()); this.registeredBean, this.instantiationDescriptor.get());
} }
@Override @Override

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier; 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.RegisteredBean.InstantiationDescriptor;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@@ -120,14 +121,29 @@ public class InstanceSupplierCodeGenerator {
* @param registeredBean the bean to handle * @param registeredBean the bean to handle
* @param constructorOrFactoryMethod the executable to use to create the bean * @param constructorOrFactoryMethod the executable to use to create the bean
* @return the generated code * @return the generated code
* @deprecated in favor of {@link #generateCode(RegisteredBean, InstantiationDescriptor)}
*/ */
@Deprecated(since = "6.1.7")
public CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) { public CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
return generateCode(registeredBean, new InstantiationDescriptor(
constructorOrFactoryMethod, constructorOrFactoryMethod.getDeclaringClass()));
}
/**
* Generate the instance supplier code.
* @param registeredBean the bean to handle
* @param instantiationDescriptor the executable to use to create the bean
* @return the generated code
* @since 6.1.7
*/
public CodeBlock generateCode(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Executable constructorOrFactoryMethod = instantiationDescriptor.executable();
registerRuntimeHintsIfNecessary(registeredBean, constructorOrFactoryMethod); registerRuntimeHintsIfNecessary(registeredBean, constructorOrFactoryMethod);
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) { if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
return generateCodeForConstructor(registeredBean, constructor); return generateCodeForConstructor(registeredBean, constructor);
} }
if (constructorOrFactoryMethod instanceof Method method) { if (constructorOrFactoryMethod instanceof Method method) {
return generateCodeForFactoryMethod(registeredBean, method); return generateCodeForFactoryMethod(registeredBean, method, instantiationDescriptor.targetClass());
} }
throw new IllegalStateException( throw new IllegalStateException(
"No suitable executor found for " + registeredBean.getBeanName()); "No suitable executor found for " + registeredBean.getBeanName());
@@ -253,21 +269,21 @@ public class InstanceSupplierCodeGenerator {
declaringClass.getSimpleName(), args); declaringClass.getSimpleName(), args);
} }
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) { private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) {
String beanName = registeredBean.getBeanName(); String beanName = registeredBean.getBeanName();
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass()); Class<?> targetClassToUse = ClassUtils.getUserClass(targetClass);
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers()); boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod); Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
if (accessVisibility != Visibility.PRIVATE) { if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleFactoryMethod( return generateCodeForAccessibleFactoryMethod(
beanName, factoryMethod, declaringClass, dependsOnBean); beanName, factoryMethod, targetClassToUse, dependsOnBean);
} }
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass); return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, targetClassToUse);
} }
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName, private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) { Method factoryMethod, Class<?> targetClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection().registerMethod( this.generationContext.getRuntimeHints().reflection().registerMethod(
factoryMethod, ExecutableMode.INTROSPECT); factoryMethod, ExecutableMode.INTROSPECT);
@@ -276,20 +292,20 @@ public class InstanceSupplierCodeGenerator {
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeBlock.Builder code = CodeBlock.builder(); CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class, code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
suppliedType, declaringClass, factoryMethod.getName()); suppliedType, targetClass, factoryMethod.getName());
code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME, code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME,
declaringClass, factoryMethod.getName()); targetClass, factoryMethod.getName());
return code.build(); return code.build();
} }
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod, buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
declaringClass, dependsOnBean, PRIVATE_STATIC)); targetClass, dependsOnBean, PRIVATE_STATIC));
return generateReturnStatement(getInstanceMethod); return generateReturnStatement(getInstanceMethod);
} }
private CodeBlock generateCodeForInaccessibleFactoryMethod( private CodeBlock generateCodeForInaccessibleFactoryMethod(
String beanName, Method factoryMethod, Class<?> declaringClass) { String beanName, Method factoryMethod, Class<?> targetClass) {
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE); this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> { GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
@@ -298,19 +314,19 @@ public class InstanceSupplierCodeGenerator {
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType)); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
method.addStatement(generateInstanceSupplierForFactoryMethod( method.addStatement(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, declaringClass, factoryMethod.getName())); factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
}); });
return generateReturnStatement(getInstanceMethod); return generateReturnStatement(getInstanceMethod);
} }
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method, private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
String beanName, Method factoryMethod, Class<?> declaringClass, String beanName, Method factoryMethod, Class<?> targetClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) { boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName(); String factoryMethodName = factoryMethod.getName();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeWarnings codeWarnings = new CodeWarnings(); CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType) codeWarnings.detectDeprecation(targetClass, factoryMethod, suppliedType)
.detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType)); .detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
@@ -320,41 +336,41 @@ public class InstanceSupplierCodeGenerator {
CodeBlock.Builder code = CodeBlock.builder(); CodeBlock.Builder code = CodeBlock.builder();
code.add(generateInstanceSupplierForFactoryMethod( code.add(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, declaringClass, factoryMethodName)); factoryMethod, suppliedType, targetClass, factoryMethodName));
boolean hasArguments = factoryMethod.getParameterCount() > 0; boolean hasArguments = factoryMethod.getParameterCount() > 0;
CodeBlock arguments = hasArguments ? CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod) new AutowiredArgumentsCodeGenerator(targetClass, factoryMethod)
.generateCode(factoryMethod.getParameterTypes()) .generateCode(factoryMethod.getParameterTypes())
: NO_ARGS; : NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForMethod( CodeBlock newInstance = generateNewInstanceCodeForMethod(
dependsOnBean, declaringClass, factoryMethodName, arguments); dependsOnBean, targetClass, factoryMethodName, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance)); code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build()); method.addStatement(code.build());
} }
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod, private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) { Class<?> suppliedType, Class<?> targetClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) { if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)", return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName); BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName);
} }
CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0); CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)", return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)",
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes); BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName, parameterTypes);
} }
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean, private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
Class<?> declaringClass, String factoryMethodName, CodeBlock args) { Class<?> targetClass, String factoryMethodName, CodeBlock args) {
if (!dependsOnBean) { if (!dependsOnBean) {
return CodeBlock.of("$T.$L($L)", declaringClass, factoryMethodName, args); return CodeBlock.of("$T.$L($L)", targetClass, factoryMethodName, args);
} }
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)", return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args); REGISTERED_BEAN_PARAMETER_NAME, targetClass, factoryMethodName, args);
} }
private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) { private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -206,12 +208,33 @@ public final class RegisteredBean {
/** /**
* Resolve the constructor or factory method to use for this bean. * Resolve the constructor or factory method to use for this bean.
* @return the {@link java.lang.reflect.Constructor} or {@link java.lang.reflect.Method} * @return the {@link java.lang.reflect.Constructor} or {@link java.lang.reflect.Method}
* @deprecated in favor of {@link #resolveInstantiationDescriptor()}
*/ */
@Deprecated(since = "6.1.7")
public Executable resolveConstructorOrFactoryMethod() { public Executable resolveConstructorOrFactoryMethod() {
return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory()) return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory())
.resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition()); .resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition());
} }
/**
* Resolve the {@linkplain InstantiationDescriptor descriptor} to use to
* instantiate this bean. It defines the {@link java.lang.reflect.Constructor}
* or {@link java.lang.reflect.Method} to use as well as additional metadata.
* @since 6.1.7
*/
public InstantiationDescriptor resolveInstantiationDescriptor() {
Executable executable = resolveConstructorOrFactoryMethod();
if (executable instanceof Method method && !Modifier.isStatic(method.getModifiers())) {
String factoryBeanName = getMergedBeanDefinition().getFactoryBeanName();
if (factoryBeanName != null && this.beanFactory.containsBean(factoryBeanName)) {
Class<?> target = this.beanFactory.getMergedBeanDefinition(factoryBeanName)
.getResolvableType().toClass();
return new InstantiationDescriptor(executable, target);
}
}
return new InstantiationDescriptor(executable, executable.getDeclaringClass());
}
/** /**
* Resolve an autowired argument. * Resolve an autowired argument.
* @param descriptor the descriptor for the dependency (field/method/constructor) * @param descriptor the descriptor for the dependency (field/method/constructor)
@@ -237,6 +260,20 @@ public final class RegisteredBean {
.append("mergedBeanDefinition", getMergedBeanDefinition()).toString(); .append("mergedBeanDefinition", getMergedBeanDefinition()).toString();
} }
/**
* Describe how a bean should be instantiated. While the {@code targetClass}
* is usually the declaring class of the {@code executable}, there are cases
* where retaining the actual concrete type is necessary.
* @param executable the {@link Executable} to invoke
* @param targetClass the target {@link Class} of the executable
* @since 6.1.7
*/
public record InstantiationDescriptor(Executable executable, Class<?> targetClass) {
public InstantiationDescriptor(Executable executable) {
this(executable, executable.getDeclaringClass());
}
}
/** /**
* Resolver used to obtain inner-bean details. * Resolver used to obtain inner-bean details.

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -161,7 +161,8 @@ class BeanDefinitionMethodGeneratorTests {
@Test @Test
void generateWithBeanClassAndFactoryMethodNameSetsTargetTypeAndBeanClass() { void generateWithBeanClassAndFactoryMethodNameSetsTargetTypeAndBeanClass() {
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration()); this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory"); beanDefinition.setFactoryBeanName("factory");
beanDefinition.setFactoryMethodName("simpleBean"); beanDefinition.setFactoryMethodName("simpleBean");
@@ -182,7 +183,8 @@ class BeanDefinitionMethodGeneratorTests {
@Test @Test
void generateWithTargetTypeAndFactoryMethodNameSetsOnlyBeanClass() { void generateWithTargetTypeAndFactoryMethodNameSetsOnlyBeanClass() {
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration()); this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition(); RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(SimpleBean.class); beanDefinition.setTargetType(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory"); beanDefinition.setFactoryBeanName("factory");

View File

@@ -31,6 +31,7 @@ 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.testfixture.beans.factory.DummyFactory; import org.springframework.beans.testfixture.beans.factory.DummyFactory;
import org.springframework.beans.testfixture.beans.factory.StringFactoryBean; import org.springframework.beans.testfixture.beans.factory.StringFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.GenericFactoryBean; import org.springframework.beans.testfixture.beans.factory.aot.GenericFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode; import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode; import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
@@ -38,6 +39,7 @@ import org.springframework.beans.testfixture.beans.factory.aot.NumberFactoryBean
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean; import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanArrayFactoryBean; import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanArrayFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanConfiguration; import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanConfiguration;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanFactoryBean; import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanFactoryBean;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName; import org.springframework.javapoet.ClassName;
@@ -126,6 +128,21 @@ class DefaultBeanRegistrationCodeFragmentsTests {
SimpleBeanConfiguration.class); SimpleBeanConfiguration.class);
} }
@Test // gh-32609
void getTargetOnMethodFromInterface() {
this.beanFactory.registerBeanDefinition("configuration",
new RootBeanDefinition(DefaultSimpleBeanContract.class));
Method method = ReflectionUtils.findMethod(SimpleBeanContract.class, "simpleBean");
assertThat(method).isNotNull();
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
applyConstructorOrFactoryMethod(beanDefinition, method);
beanDefinition.setFactoryBeanName("configuration");
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "testBean");
assertTarget(createInstance(registeredBean).getTarget(registeredBean),
DefaultSimpleBeanContract.class);
}
@Test @Test
void getTargetOnMethodWithInnerBeanInJavaPackage() { void getTargetOnMethodWithInnerBeanInJavaPackage() {
RegisteredBean registeredBean = registerTestBean(SimpleBean.class); RegisteredBean registeredBean = registerTestBean(SimpleBean.class);
@@ -190,7 +207,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
} }
@Test @Test
void customizedGetTargetDoesNotResolveConstructorOrFactoryMethod() { void customizedGetTargetDoesNotResolveInstantiationDescriptor() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class)); RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) { BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override @Override
@@ -199,11 +216,11 @@ class DefaultBeanRegistrationCodeFragmentsTests {
} }
}); });
assertTarget(customCodeFragments.getTarget(registeredBean), String.class); assertTarget(customCodeFragments.getTarget(registeredBean), String.class);
verify(registeredBean, never()).resolveConstructorOrFactoryMethod(); verify(registeredBean, never()).resolveInstantiationDescriptor();
} }
@Test @Test
void customizedGenerateInstanceSupplierCodeDoesNotResolveConstructorOrFactoryMethod() { void customizedGenerateInstanceSupplierCodeDoesNotResolveInstantiationDescriptor() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class)); RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) { BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override @Override
@@ -214,7 +231,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}); });
assertThat(customCodeFragments.generateInstanceSupplierCode(this.generationContext, assertThat(customCodeFragments.generateInstanceSupplierCode(this.generationContext,
new MockBeanRegistrationCode(this.generationContext), false)).hasToString("// Hello"); new MockBeanRegistrationCode(this.generationContext), false)).hasToString("// Hello");
verify(registeredBean, never()).resolveConstructorOrFactoryMethod(); verify(registeredBean, never()).resolveInstantiationDescriptor();
} }
private BeanRegistrationCodeFragments createCustomCodeFragments(RegisteredBean registeredBean, UnaryOperator<BeanRegistrationCodeFragments> customFragments) { private BeanRegistrationCodeFragments createCustomCodeFragments(RegisteredBean registeredBean, UnaryOperator<BeanRegistrationCodeFragments> customFragments) {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.beans.factory.aot; package org.springframework.beans.factory.aot;
import java.lang.reflect.Executable;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -38,10 +37,14 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier; 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.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.TestBeanWithPrivateConstructor; import org.springframework.beans.testfixture.beans.TestBeanWithPrivateConstructor;
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder; import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration; import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent; import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent; import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
@@ -185,6 +188,23 @@ class InstanceSupplierCodeGeneratorTests {
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
} }
@Test
void generateWhenHasFactoryMethodOnInterface() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(SimpleBean.class)
.setFactoryMethodOnBean("simpleBean", "config").getBeanDefinition();
this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.rootBeanDefinition(DefaultSimpleBeanContract.class).getBeanDefinition());
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(SimpleBean.class);
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(DefaultSimpleBeanContract.class).simpleBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
}
@Test @Test
void generateWhenHasPrivateStaticFactoryMethodWithNoArg() { void generateWhenHasPrivateStaticFactoryMethodWithNoArg() {
BeanDefinition beanDefinition = BeanDefinitionBuilder BeanDefinition beanDefinition = BeanDefinitionBuilder
@@ -402,9 +422,9 @@ class InstanceSupplierCodeGeneratorTests {
InstanceSupplierCodeGenerator generator = new InstanceSupplierCodeGenerator( InstanceSupplierCodeGenerator generator = new InstanceSupplierCodeGenerator(
this.generationContext, generateClass.getName(), this.generationContext, generateClass.getName(),
generateClass.getMethods(), false); generateClass.getMethods(), false);
Executable constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod(); InstantiationDescriptor instantiationDescriptor = registeredBean.resolveInstantiationDescriptor();
assertThat(constructorOrFactoryMethod).isNotNull(); assertThat(instantiationDescriptor).isNotNull();
CodeBlock generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod); CodeBlock generatedCode = generator.generateCode(registeredBean, instantiationDescriptor);
typeBuilder.set(type -> { typeBuilder.set(type -> {
type.addModifiers(Modifier.PUBLIC); type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(ParameterizedTypeName.get(Supplier.class, InstanceSupplier.class)); type.addSuperinterface(ParameterizedTypeName.get(Supplier.class, InstanceSupplier.class));

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -116,9 +116,9 @@ class InstanceSupplierCodeGeneratorKotlinTests {
generationContext, generateClass.name, generationContext, generateClass.name,
generateClass.methods, false generateClass.methods, false
) )
val constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod() val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(constructorOrFactoryMethod).isNotNull() Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod) val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder -> typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC) type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface( type.addSuperinterface(

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.testfixture.beans.factory.aot;
public class DefaultSimpleBeanContract implements SimpleBeanContract {
public SimpleBean anotherSimpleBean() {
return new SimpleBean();
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.testfixture.beans.factory.aot;
/**
* Showcase a factory method that is defined on an interface.
*
* @author Stephane Nicoll
*/
public interface SimpleBeanContract {
default SimpleBean simpleBean() {
return new SimpleBean();
}
}

View File

@@ -75,6 +75,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationStartupAware; import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
@@ -795,24 +796,26 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) { BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
Executable executableToUse = proxyExecutable(generationContext.getRuntimeHints(), InstantiationDescriptor instantiationDescriptor = proxyInstantiationDescriptor(
this.registeredBean.resolveConstructorOrFactoryMethod()); generationContext.getRuntimeHints(), this.registeredBean.resolveInstantiationDescriptor());
return new InstanceSupplierCodeGenerator(generationContext, return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut) beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, executableToUse); .generateCode(this.registeredBean, instantiationDescriptor);
} }
private Executable proxyExecutable(RuntimeHints runtimeHints, Executable userExecutable) { private InstantiationDescriptor proxyInstantiationDescriptor(RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
Executable userExecutable = instantiationDescriptor.executable();
if (userExecutable instanceof Constructor<?> userConstructor) { if (userExecutable instanceof Constructor<?> userConstructor) {
try { try {
runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT); runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT);
return this.proxyClass.getConstructor(userExecutable.getParameterTypes()); Constructor<?> constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes());
return new InstantiationDescriptor(constructor);
} }
catch (NoSuchMethodException ex) { catch (NoSuchMethodException ex) {
throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex); throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex);
} }
} }
return userExecutable; return instantiationDescriptor;
} }
} }

View File

@@ -432,7 +432,6 @@ class TestContextAotGeneratorIntegrationTests extends AbstractAotTests {
"org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext006_BeanDefinitions.java", "org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext006_BeanDefinitions.java",
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_Autowiring.java", "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_Autowiring.java",
"org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_BeanDefinitions.java", "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration__TestContext006_BeanDefinitions.java",
"org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport__TestContext006_BeanDefinitions.java",
// XmlSpringJupiterTests // XmlSpringJupiterTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext007_BeanDefinitions.java", "org/springframework/context/event/DefaultEventListenerFactory__TestContext007_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext007_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext007_BeanDefinitions.java",

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import org.springframework.aot.generate.GenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode; import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.aot.ApplicationContextAotGenerator;
@@ -44,7 +45,8 @@ public class ManagementConfiguration {
@Bean @Bean
static BeanRegistrationAotProcessor beanRegistrationAotProcessor() { static BeanRegistrationAotProcessor beanRegistrationAotProcessor() {
return registeredBean -> { return registeredBean -> {
Executable factoryMethod = registeredBean.resolveConstructorOrFactoryMethod(); InstantiationDescriptor instantiationDescriptor = registeredBean.resolveInstantiationDescriptor();
Executable factoryMethod = instantiationDescriptor.executable();
// Make AOT contribution for @Managed @Bean methods. // Make AOT contribution for @Managed @Bean methods.
if (AnnotatedElementUtils.hasAnnotation(factoryMethod, Managed.class)) { if (AnnotatedElementUtils.hasAnnotation(factoryMethod, Managed.class)) {
return new AotContribution(createManagementContext()); return new AotContribution(createManagementContext());