Restore special instance supplier generation for inner classes

Closes gh-33683
This commit is contained in:
Juergen Hoeller 2024-10-11 16:59:15 +02:00
parent b748cb38c5
commit 7ea0ac55cd
4 changed files with 96 additions and 46 deletions

View File

@ -276,7 +276,9 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable); ValueHolder[] argumentValues = resolveArgumentValues(registeredBean, executable);
Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2); Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2);
for (int i = 0; i < parameterCount; i++) { int startIndex = (executable instanceof Constructor<?> constructor &&
ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0;
for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i); MethodParameter parameter = getMethodParameter(executable, i);
DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true); DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true);
String shortcut = (this.shortcutBeanNames != null ? this.shortcutBeanNames[i] : null); String shortcut = (this.shortcutBeanNames != null ? this.shortcutBeanNames[i] : null);

View File

@ -99,7 +99,7 @@ public class InstanceSupplierCodeGenerator {
/** /**
* Create a new instance. * Create a new generator instance.
* @param generationContext the generation context * @param generationContext the generation context
* @param className the class name of the bean to instantiate * @param className the class name of the bean to instantiate
* @param generatedMethods the generated methods * @param generatedMethods the generated methods
@ -158,47 +158,43 @@ public class InstanceSupplierCodeGenerator {
private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Constructor<?> constructor) { private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Constructor<?> constructor) {
String beanName = registeredBean.getBeanName(); String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass(); Class<?> beanClass = registeredBean.getBeanClass();
Class<?> declaringClass = constructor.getDeclaringClass();
Visibility accessVisibility = getAccessVisibility(registeredBean, constructor);
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(beanClass)) { if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(beanClass)) {
return generateCodeForInaccessibleConstructor(beanName, beanClass, constructor, return generateCodeForInaccessibleConstructor(beanName, constructor,
hints -> hints.registerType(beanClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); hints -> hints.registerType(beanClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
} }
else if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleConstructor(beanName, beanClass, constructor, declaringClass); if (!isVisible(constructor, constructor.getDeclaringClass())) {
return generateCodeForInaccessibleConstructor(beanName, constructor,
hints -> hints.registerConstructor(constructor, ExecutableMode.INVOKE));
} }
return generateCodeForInaccessibleConstructor(beanName, beanClass, constructor, return generateCodeForAccessibleConstructor(beanName, constructor);
hints -> hints.registerConstructor(constructor, ExecutableMode.INVOKE));
} }
private CodeBlock generateCodeForAccessibleConstructor(String beanName, Class<?> beanClass, private CodeBlock generateCodeForAccessibleConstructor(String beanName, Constructor<?> constructor) {
Constructor<?> constructor, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection().registerConstructor( this.generationContext.getRuntimeHints().reflection().registerConstructor(
constructor, ExecutableMode.INTROSPECT); constructor, ExecutableMode.INTROSPECT);
if (constructor.getParameterCount() == 0) { if (constructor.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) { if (!this.allowDirectSupplierShortcut) {
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, declaringClass); return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, constructor.getDeclaringClass());
} }
if (!isThrowingCheckedException(constructor)) { if (!isThrowingCheckedException(constructor)) {
return CodeBlock.of("$T::new", declaringClass); return CodeBlock.of("$T::new", constructor.getDeclaringClass());
} }
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, declaringClass); return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, constructor.getDeclaringClass());
} }
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForConstructor( buildGetInstanceMethodForConstructor(method, beanName, constructor, PRIVATE_STATIC));
method, beanName, beanClass, constructor, declaringClass, PRIVATE_STATIC));
return generateReturnStatement(generatedMethod); return generateReturnStatement(generatedMethod);
} }
private CodeBlock generateCodeForInaccessibleConstructor(String beanName, Class<?> beanClass, private CodeBlock generateCodeForInaccessibleConstructor(String beanName,
Constructor<?> constructor, Consumer<ReflectionHints> hints) { Constructor<?> constructor, Consumer<ReflectionHints> hints) {
CodeWarnings codeWarnings = new CodeWarnings(); CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(beanClass, constructor) codeWarnings.detectDeprecation(constructor.getDeclaringClass(), constructor)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType)); .detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
hints.accept(this.generationContext.getRuntimeHints().reflection()); hints.accept(this.generationContext.getRuntimeHints().reflection());
@ -206,32 +202,34 @@ public class InstanceSupplierCodeGenerator {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC); method.addModifiers(PRIVATE_STATIC);
codeWarnings.suppress(method); codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, constructor.getDeclaringClass()));
method.addStatement(generateResolverForConstructor(beanClass, constructor)); method.addStatement(generateResolverForConstructor(constructor));
}); });
return generateReturnStatement(generatedMethod); return generateReturnStatement(generatedMethod);
} }
private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, String beanName,
String beanName, Class<?> beanClass, Constructor<?> constructor, Class<?> declaringClass, Constructor<?> constructor, javax.lang.model.element.Modifier... modifiers) {
javax.lang.model.element.Modifier... modifiers) {
Class<?> declaringClass = constructor.getDeclaringClass();
CodeWarnings codeWarnings = new CodeWarnings(); CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(beanClass, constructor, declaringClass) codeWarnings.detectDeprecation(declaringClass, constructor)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType)); .detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers); method.addModifiers(modifiers);
codeWarnings.suppress(method); codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass)); method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, declaringClass));
CodeBlock.Builder code = CodeBlock.builder(); CodeBlock.Builder code = CodeBlock.builder();
code.add(generateResolverForConstructor(beanClass, constructor)); code.add(generateResolverForConstructor(constructor));
boolean hasArguments = constructor.getParameterCount() > 0; boolean hasArguments = constructor.getParameterCount() > 0;
boolean onInnerClass = ClassUtils.isInnerClass(declaringClass);
CodeBlock arguments = hasArguments ? CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(declaringClass, constructor) new AutowiredArgumentsCodeGenerator(declaringClass, constructor)
.generateCode(constructor.getParameterTypes()) .generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0))
: NO_ARGS; : NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForConstructor(declaringClass, arguments); CodeBlock newInstance = generateNewInstanceCodeForConstructor(declaringClass, arguments);
@ -239,24 +237,29 @@ public class InstanceSupplierCodeGenerator {
method.addStatement(code.build()); method.addStatement(code.build());
} }
private CodeBlock generateResolverForConstructor(Class<?> beanClass, Constructor<?> constructor) { private CodeBlock generateResolverForConstructor(Constructor<?> constructor) {
CodeBlock parameterTypes = generateParameterTypesCode(constructor.getParameterTypes()); CodeBlock parameterTypes = generateParameterTypesCode(constructor.getParameterTypes());
return CodeBlock.of("return $T.<$T>forConstructor($L)", BeanInstanceSupplier.class, beanClass, parameterTypes); return CodeBlock.of("return $T.<$T>forConstructor($L)", BeanInstanceSupplier.class,
constructor.getDeclaringClass(), parameterTypes);
} }
private CodeBlock generateNewInstanceCodeForConstructor(Class<?> declaringClass, CodeBlock args) { private CodeBlock generateNewInstanceCodeForConstructor(Class<?> declaringClass, CodeBlock args) {
if (ClassUtils.isInnerClass(declaringClass)) {
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).new $L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass.getEnclosingClass(),
declaringClass.getSimpleName(), args);
}
return CodeBlock.of("new $T($L)", declaringClass, args); return CodeBlock.of("new $T($L)", declaringClass, args);
} }
private CodeBlock generateCodeForFactoryMethod( private CodeBlock generateCodeForFactoryMethod(
RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) { RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) {
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod); if (!isVisible(factoryMethod, targetClass)) {
if (accessVisibility != Visibility.PRIVATE) { return generateCodeForInaccessibleFactoryMethod(registeredBean.getBeanName(), factoryMethod, targetClass);
return generateCodeForAccessibleFactoryMethod(registeredBean.getBeanName(), factoryMethod, targetClass,
registeredBean.getMergedBeanDefinition().getFactoryBeanName());
} }
return generateCodeForInaccessibleFactoryMethod(registeredBean.getBeanName(), factoryMethod, targetClass); return generateCodeForAccessibleFactoryMethod(registeredBean.getBeanName(), factoryMethod, targetClass,
registeredBean.getMergedBeanDefinition().getFactoryBeanName());
} }
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName, private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
@ -366,11 +369,13 @@ public class InstanceSupplierCodeGenerator {
return code.build(); return code.build();
} }
private Visibility getAccessVisibility(RegisteredBean registeredBean, Member member) { private boolean isVisible(Member member, Class<?> targetClass) {
AccessControl beanTypeAccessControl = AccessControl.forResolvableType(registeredBean.getBeanType()); AccessControl classAccessControl = AccessControl.forClass(targetClass);
AccessControl memberAccessControl = AccessControl.forMember(member); AccessControl memberAccessControl = AccessControl.forMember(member);
return AccessControl.lowest(beanTypeAccessControl, memberAccessControl).getVisibility(); Visibility visibility = AccessControl.lowest(classAccessControl, memberAccessControl).getVisibility();
} return (visibility == Visibility.PUBLIC || (visibility != Visibility.PRIVATE &&
member.getDeclaringClass().getPackageName().equals(this.className.packageName())));
}
private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes) { private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes) {
CodeBlock.Builder code = CodeBlock.builder(); CodeBlock.Builder code = CodeBlock.builder();
@ -392,6 +397,7 @@ public class InstanceSupplierCodeGenerator {
.anyMatch(Exception.class::isAssignableFrom); .anyMatch(Exception.class::isAssignableFrom);
} }
/** /**
* Inner class to avoid a hard dependency on Kotlin at runtime. * Inner class to avoid a hard dependency on Kotlin at runtime.
*/ */
@ -410,7 +416,6 @@ public class InstanceSupplierCodeGenerator {
} }
return false; return false;
} }
} }

View File

@ -47,7 +47,9 @@ 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.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.EnvironmentAwareComponentWithoutPublicConstructor;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent; import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponentWithoutPublicConstructor;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration; import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean; import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean;
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedConstructor; import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedConstructor;
@ -119,10 +121,10 @@ class InstanceSupplierCodeGeneratorTests {
RootBeanDefinition beanDefinition = new RootBeanDefinition(NoDependencyComponent.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(NoDependencyComponent.class);
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
compile(beanDefinition, (instanceSupplier, compiled) -> { compile(beanDefinition, (instanceSupplier, compiled) -> {
NoDependencyComponent bean = getBean(beanDefinition, instanceSupplier); Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(NoDependencyComponent.class); assertThat(bean).isInstanceOf(NoDependencyComponent.class);
assertThat(compiled.getSourceFile()).contains( assertThat(compiled.getSourceFile()).contains(
"InstanceSupplier.using(InnerComponentConfiguration.NoDependencyComponent::new"); "getBeanFactory().getBean(InnerComponentConfiguration.class).new NoDependencyComponent()");
}); });
assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class)) assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
@ -134,15 +136,44 @@ class InstanceSupplierCodeGeneratorTests {
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration()); this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
this.beanFactory.registerSingleton("environment", new StandardEnvironment()); this.beanFactory.registerSingleton("environment", new StandardEnvironment());
compile(beanDefinition, (instanceSupplier, compiled) -> { compile(beanDefinition, (instanceSupplier, compiled) -> {
EnvironmentAwareComponent bean = getBean(beanDefinition, instanceSupplier); Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(EnvironmentAwareComponent.class); assertThat(bean).isInstanceOf(EnvironmentAwareComponent.class);
assertThat(compiled.getSourceFile()).contains( assertThat(compiled.getSourceFile()).contains(
"new InnerComponentConfiguration.EnvironmentAwareComponent("); "getBeanFactory().getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent(");
}); });
assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class)) assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT));
} }
@Test
void generateWhenHasNonPublicConstructorWithInnerClassAndDefaultConstructor() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(NoDependencyComponentWithoutPublicConstructor.class);
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(NoDependencyComponentWithoutPublicConstructor.class);
assertThat(compiled.getSourceFile()).doesNotContain(
"getBeanFactory().getBean(InnerComponentConfiguration.class)");
});
assertThat(getReflectionHints().getTypeHint(NoDependencyComponentWithoutPublicConstructor.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INVOKE));
}
@Test
void generateWhenHasNonPublicConstructorWithInnerClassAndParameter() {
BeanDefinition beanDefinition = new RootBeanDefinition(EnvironmentAwareComponentWithoutPublicConstructor.class);
this.beanFactory.registerSingleton("configuration", new InnerComponentConfiguration());
this.beanFactory.registerSingleton("environment", new StandardEnvironment());
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(EnvironmentAwareComponentWithoutPublicConstructor.class);
assertThat(compiled.getSourceFile()).doesNotContain(
"getBeanFactory().getBean(InnerComponentConfiguration.class)");
});
assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponentWithoutPublicConstructor.class))
.satisfies(hasConstructorWithMode(ExecutableMode.INVOKE));
}
@Test @Test
void generateWhenHasConstructorWithGeneric() { void generateWhenHasConstructorWithGeneric() {
BeanDefinition beanDefinition = new RootBeanDefinition(NumberHolderFactoryBean.class); BeanDefinition beanDefinition = new RootBeanDefinition(NumberHolderFactoryBean.class);

View File

@ -20,16 +20,28 @@ import org.springframework.core.env.Environment;
public class InnerComponentConfiguration { public class InnerComponentConfiguration {
public static class NoDependencyComponent { public class NoDependencyComponent {
public NoDependencyComponent() { public NoDependencyComponent() {
} }
} }
public static class EnvironmentAwareComponent { public class EnvironmentAwareComponent {
public EnvironmentAwareComponent(Environment environment) { public EnvironmentAwareComponent(Environment environment) {
} }
} }
public class NoDependencyComponentWithoutPublicConstructor {
NoDependencyComponentWithoutPublicConstructor() {
}
}
public class EnvironmentAwareComponentWithoutPublicConstructor {
EnvironmentAwareComponentWithoutPublicConstructor(Environment environment) {
}
}
} }