diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index e9a4f40bca66..b70c6f287dab 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -156,91 +156,96 @@ private void registerRuntimeHintsIfNecessary(RegisteredBean registeredBean, Exec } private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Constructor constructor) { - String beanName = registeredBean.getBeanName(); - Class beanClass = registeredBean.getBeanClass(); + ConstructorDescriptor descriptor = new ConstructorDescriptor( + registeredBean.getBeanName(), constructor, registeredBean.getBeanClass()); - if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(beanClass)) { - return generateCodeForInaccessibleConstructor(beanName, constructor, - hints -> hints.registerType(beanClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); + Class publicType = descriptor.publicType(); + if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) { + return generateCodeForInaccessibleConstructor(descriptor, + hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); } if (!isVisible(constructor, constructor.getDeclaringClass())) { - return generateCodeForInaccessibleConstructor(beanName, constructor, + return generateCodeForInaccessibleConstructor(descriptor, hints -> hints.registerConstructor(constructor, ExecutableMode.INVOKE)); } - return generateCodeForAccessibleConstructor(beanName, constructor); + return generateCodeForAccessibleConstructor(descriptor); } - private CodeBlock generateCodeForAccessibleConstructor(String beanName, Constructor constructor) { + private CodeBlock generateCodeForAccessibleConstructor(ConstructorDescriptor descriptor) { + Constructor constructor = descriptor.constructor(); this.generationContext.getRuntimeHints().reflection().registerConstructor( constructor, ExecutableMode.INTROSPECT); if (constructor.getParameterCount() == 0) { if (!this.allowDirectSupplierShortcut) { - return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, constructor.getDeclaringClass()); + return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, descriptor.actualType()); } if (!isThrowingCheckedException(constructor)) { - return CodeBlock.of("$T::new", constructor.getDeclaringClass()); + return CodeBlock.of("$T::new", descriptor.actualType()); } - return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, constructor.getDeclaringClass()); + return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, descriptor.actualType()); } GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> - buildGetInstanceMethodForConstructor(method, beanName, constructor, PRIVATE_STATIC)); + buildGetInstanceMethodForConstructor(method, descriptor, PRIVATE_STATIC)); return generateReturnStatement(generatedMethod); } - private CodeBlock generateCodeForInaccessibleConstructor(String beanName, - Constructor constructor, Consumer hints) { + private CodeBlock generateCodeForInaccessibleConstructor(ConstructorDescriptor descriptor, + Consumer hints) { + Constructor constructor = descriptor.constructor(); CodeWarnings codeWarnings = new CodeWarnings(); codeWarnings.detectDeprecation(constructor.getDeclaringClass(), constructor) .detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType)); hints.accept(this.generationContext.getRuntimeHints().reflection()); GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> { - method.addJavadoc("Get the bean instance supplier for '$L'.", beanName); + method.addJavadoc("Get the bean instance supplier for '$L'.", descriptor.beanName()); method.addModifiers(PRIVATE_STATIC); codeWarnings.suppress(method); - method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, constructor.getDeclaringClass())); - method.addStatement(generateResolverForConstructor(constructor)); + method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, descriptor.publicType())); + method.addStatement(generateResolverForConstructor(descriptor)); }); return generateReturnStatement(generatedMethod); } - private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, String beanName, - Constructor constructor, javax.lang.model.element.Modifier... modifiers) { + private void buildGetInstanceMethodForConstructor(MethodSpec.Builder method, ConstructorDescriptor descriptor, + javax.lang.model.element.Modifier... modifiers) { - Class declaringClass = constructor.getDeclaringClass(); + Constructor constructor = descriptor.constructor(); + Class publicType = descriptor.publicType(); + Class actualType = descriptor.actualType(); CodeWarnings codeWarnings = new CodeWarnings(); - codeWarnings.detectDeprecation(declaringClass, constructor) + codeWarnings.detectDeprecation(actualType, constructor) .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'.", descriptor.beanName()); method.addModifiers(modifiers); codeWarnings.suppress(method); - method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, declaringClass)); + method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, publicType)); CodeBlock.Builder code = CodeBlock.builder(); - code.add(generateResolverForConstructor(constructor)); + code.add(generateResolverForConstructor(descriptor)); boolean hasArguments = constructor.getParameterCount() > 0; - boolean onInnerClass = ClassUtils.isInnerClass(declaringClass); + boolean onInnerClass = ClassUtils.isInnerClass(actualType); CodeBlock arguments = hasArguments ? - new AutowiredArgumentsCodeGenerator(declaringClass, constructor) + new AutowiredArgumentsCodeGenerator(actualType, constructor) .generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0)) : NO_ARGS; - CodeBlock newInstance = generateNewInstanceCodeForConstructor(declaringClass, arguments); + CodeBlock newInstance = generateNewInstanceCodeForConstructor(actualType, arguments); code.add(generateWithGeneratorCode(hasArguments, newInstance)); method.addStatement(code.build()); } - private CodeBlock generateResolverForConstructor(Constructor constructor) { - CodeBlock parameterTypes = generateParameterTypesCode(constructor.getParameterTypes()); + private CodeBlock generateResolverForConstructor(ConstructorDescriptor descriptor) { + CodeBlock parameterTypes = generateParameterTypesCode(descriptor.constructor().getParameterTypes()); return CodeBlock.of("return $T.<$T>forConstructor($L)", BeanInstanceSupplier.class, - constructor.getDeclaringClass(), parameterTypes); + descriptor.publicType(), parameterTypes); } private CodeBlock generateNewInstanceCodeForConstructor(Class declaringClass, CodeBlock args) { @@ -438,4 +443,11 @@ private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescr } } + record ConstructorDescriptor(String beanName, Constructor constructor, Class publicType) { + + Class actualType() { + return this.constructor.getDeclaringClass(); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java index 01c2ea37b943..277ea2952c66 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java @@ -66,8 +66,10 @@ import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.context.testfixture.context.annotation.AutowiredCglibConfiguration; import org.springframework.context.testfixture.context.annotation.AutowiredComponent; import org.springframework.context.testfixture.context.annotation.AutowiredGenericTemplate; +import org.springframework.context.testfixture.context.annotation.AutowiredMixedCglibConfiguration; import org.springframework.context.testfixture.context.annotation.CglibConfiguration; import org.springframework.context.testfixture.context.annotation.ConfigurableCglibConfiguration; import org.springframework.context.testfixture.context.annotation.GenericTemplateConfiguration; @@ -464,6 +466,33 @@ void processAheadOfTimeWhenHasCglibProxyUseProxy() { }); } + @Test + void processAheadOfTimeWhenHasCglibProxyAndAutowiring() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(AutowiredCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { + context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi")); + initializer.initialize(context); + }); + assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi World"); + }); + } + + @Test + void processAheadOfTimeWhenHasCglibProxyAndMixedAutowiring() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(AutowiredMixedCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { + context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi") + .withProperty("world", "AOT World")); + initializer.initialize(context); + }); + assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi AOT World"); + }); + } + @Test void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredCglibConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredCglibConfiguration.java new file mode 100644 index 000000000000..f5fef28873cb --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredCglibConfiguration.java @@ -0,0 +1,35 @@ +/* + * 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.context.testfixture.context.annotation; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +public class AutowiredCglibConfiguration { + + @Autowired + private Environment environment; + + @Bean + public String text() { + return this.environment.getProperty("hello") + " World"; + } + +} diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredMixedCglibConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredMixedCglibConfiguration.java new file mode 100644 index 000000000000..6543a55ed4c0 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredMixedCglibConfiguration.java @@ -0,0 +1,41 @@ +/* + * 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.context.testfixture.context.annotation; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +public class AutowiredMixedCglibConfiguration { + + @Value("${world:World}") + private String world; + + private final Environment environment; + + public AutowiredMixedCglibConfiguration(Environment environment) { + this.environment = environment; + } + + @Bean + public String text() { + return this.environment.getProperty("hello") + " " + this.world; + } + +}