Restore user type in generated root bean definitions

This commit restores the user class in generated RootBeanDefinition
instances. Previously the CGLIB subclass was exposed. While this is
important in regular runtime as the configuration class parser operates
on the bean definition, this is not relevant for AOT as this information
is internal and captured in the instance supplier.

Closes gh-33960
This commit is contained in:
Stéphane Nicoll
2024-12-06 15:34:00 +01:00
parent d26ee8f852
commit 0d72477742
3 changed files with 94 additions and 2 deletions

View File

@@ -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()));

View File

@@ -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<GenericApplicationContext> 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

View File

@@ -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;
}
}