diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java index c34331f8535..498b04633b4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java @@ -123,7 +123,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme CodeBlock.Builder code = CodeBlock.builder(); RootBeanDefinition mbd = this.registeredBean.getMergedBeanDefinition(); - Class beanClass = (mbd.hasBeanClass() ? mbd.getBeanClass() : null); + Class beanClass = (mbd.hasBeanClass() ? ClassUtils.getUserClass(mbd.getBeanClass()) : null); CodeBlock beanClassCode = generateBeanClassCode( beanRegistrationCode.getClassName().packageName(), (beanClass != null ? beanClass : beanType.toClass())); 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 277ea2952c6..b2da6f1c7ba 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 @@ -84,6 +84,7 @@ import org.springframework.context.testfixture.context.annotation.LazyResourceMe import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration; import org.springframework.context.testfixture.context.annotation.QualifierConfiguration; import org.springframework.context.testfixture.context.annotation.ResourceComponent; +import org.springframework.context.testfixture.context.annotation.ValueCglibConfiguration; import org.springframework.context.testfixture.context.generator.SimpleComponent; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -438,12 +439,14 @@ class ApplicationContextAotGeneratorTests { @CompileWithForkedClassLoader class ConfigurationClassCglibProxy { + private static final String CGLIB_CONFIGURATION_CLASS_SUFFIX = "$$SpringCGLIB$$0"; + @Test void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(CglibConfiguration.class); TestGenerationContext context = processAheadOfTime(applicationContext); - isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$0"); + isRegisteredCglibClass(context, CglibConfiguration.class.getName() + CGLIB_CONFIGURATION_CLASS_SUFFIX); isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$0"); isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$1"); } @@ -455,6 +458,43 @@ class ApplicationContextAotGeneratorTests { .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints()); } + @Test + void processAheadOfTimeExposeUserClassForCglibProxy() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean("config", ValueCglibConfiguration.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", ValueCglibConfiguration.class)); + assertThat(compiled.getSourceFile(".*ValueCglibConfiguration__BeanDefinitions")) + .contains("new RootBeanDefinition(ValueCglibConfiguration.class)") + .contains("new %s(".formatted(toCglibClassSimpleName(ValueCglibConfiguration.class))); + }); + } + + @Test + void processAheadOfTimeUsesCglibClassForFactoryMethod() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean("config", CglibConfiguration.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", CglibConfiguration.class)); + assertThat(compiled.getSourceFile(".*CglibConfiguration__BeanDefinitions")) + .contains("new RootBeanDefinition(CglibConfiguration.class)") + .contains(">forFactoryMethod(%s.class,".formatted(toCglibClassSimpleName(CglibConfiguration.class))) + .doesNotContain(">forFactoryMethod(%s.class,".formatted(CglibConfiguration.class)); + }); + } + + private Consumer hasBeanDefinitionOfBeanClass(String name, Class beanClass) { + return context -> { + assertThat(context.containsBean(name)).isTrue(); + assertThat(context.getBeanDefinition(name)).isInstanceOfSatisfying(RootBeanDefinition.class, + rbd -> assertThat(rbd.getBeanClass()).isEqualTo(beanClass)); + }; + } + @Test void processAheadOfTimeWhenHasCglibProxyUseProxy() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); @@ -493,6 +533,20 @@ class ApplicationContextAotGeneratorTests { }); } + @Test + void processAheadOfTimeWhenHasCglibProxyWithAnnotationsOnTheUserClasConstructor() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean("config", ValueCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { + context.setEnvironment(new MockEnvironment().withProperty("name", "AOT World")); + initializer.initialize(context); + }); + assertThat(freshApplicationContext.getBean(ValueCglibConfiguration.class) + .getName()).isEqualTo("AOT World"); + }); + } + @Test void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); @@ -516,6 +570,10 @@ class ApplicationContextAotGeneratorTests { .accepts(generationContext.getRuntimeHints()); } + private String toCglibClassSimpleName(Class configClass) { + return configClass.getSimpleName() + CGLIB_CONFIGURATION_CLASS_SUFFIX; + } + } @Nested diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/ValueCglibConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/ValueCglibConfiguration.java new file mode 100644 index 00000000000..506e99e56a9 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/ValueCglibConfiguration.java @@ -0,0 +1,34 @@ +/* + * 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.Configuration; + +@Configuration +public class ValueCglibConfiguration { + + private final String name; + + public ValueCglibConfiguration(@Value("${name:World}") String name) { + this.name = name; + } + + public String getName() { + return this.name; + } +}