Skip to content

Commit 0d72477

Browse files
committed
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
1 parent d26ee8f commit 0d72477

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationConte
123123

124124
CodeBlock.Builder code = CodeBlock.builder();
125125
RootBeanDefinition mbd = this.registeredBean.getMergedBeanDefinition();
126-
Class<?> beanClass = (mbd.hasBeanClass() ? mbd.getBeanClass() : null);
126+
Class<?> beanClass = (mbd.hasBeanClass() ? ClassUtils.getUserClass(mbd.getBeanClass()) : null);
127127
CodeBlock beanClassCode = generateBeanClassCode(
128128
beanRegistrationCode.getClassName().packageName(),
129129
(beanClass != null ? beanClass : beanType.toClass()));

spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java

+59-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration;
8585
import org.springframework.context.testfixture.context.annotation.QualifierConfiguration;
8686
import org.springframework.context.testfixture.context.annotation.ResourceComponent;
87+
import org.springframework.context.testfixture.context.annotation.ValueCglibConfiguration;
8788
import org.springframework.context.testfixture.context.generator.SimpleComponent;
8889
import org.springframework.core.env.ConfigurableEnvironment;
8990
import org.springframework.core.env.Environment;
@@ -438,12 +439,14 @@ void processAheadOfTimeWithExplicitResolvableType() {
438439
@CompileWithForkedClassLoader
439440
class ConfigurationClassCglibProxy {
440441

442+
private static final String CGLIB_CONFIGURATION_CLASS_SUFFIX = "$$SpringCGLIB$$0";
443+
441444
@Test
442445
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
443446
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
444447
applicationContext.registerBean(CglibConfiguration.class);
445448
TestGenerationContext context = processAheadOfTime(applicationContext);
446-
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$0");
449+
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + CGLIB_CONFIGURATION_CLASS_SUFFIX);
447450
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$0");
448451
isRegisteredCglibClass(context, CglibConfiguration.class.getName() + "$$SpringCGLIB$$FastClass$$1");
449452
}
@@ -455,6 +458,43 @@ private void isRegisteredCglibClass(TestGenerationContext context, String cglibC
455458
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
456459
}
457460

461+
@Test
462+
void processAheadOfTimeExposeUserClassForCglibProxy() {
463+
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
464+
applicationContext.registerBean("config", ValueCglibConfiguration.class);
465+
466+
testCompiledResult(applicationContext, (initializer, compiled) -> {
467+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
468+
assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", ValueCglibConfiguration.class));
469+
assertThat(compiled.getSourceFile(".*ValueCglibConfiguration__BeanDefinitions"))
470+
.contains("new RootBeanDefinition(ValueCglibConfiguration.class)")
471+
.contains("new %s(".formatted(toCglibClassSimpleName(ValueCglibConfiguration.class)));
472+
});
473+
}
474+
475+
@Test
476+
void processAheadOfTimeUsesCglibClassForFactoryMethod() {
477+
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
478+
applicationContext.registerBean("config", CglibConfiguration.class);
479+
480+
testCompiledResult(applicationContext, (initializer, compiled) -> {
481+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
482+
assertThat(freshApplicationContext).satisfies(hasBeanDefinitionOfBeanClass("config", CglibConfiguration.class));
483+
assertThat(compiled.getSourceFile(".*CglibConfiguration__BeanDefinitions"))
484+
.contains("new RootBeanDefinition(CglibConfiguration.class)")
485+
.contains(">forFactoryMethod(%s.class,".formatted(toCglibClassSimpleName(CglibConfiguration.class)))
486+
.doesNotContain(">forFactoryMethod(%s.class,".formatted(CglibConfiguration.class));
487+
});
488+
}
489+
490+
private Consumer<GenericApplicationContext> hasBeanDefinitionOfBeanClass(String name, Class<?> beanClass) {
491+
return context -> {
492+
assertThat(context.containsBean(name)).isTrue();
493+
assertThat(context.getBeanDefinition(name)).isInstanceOfSatisfying(RootBeanDefinition.class,
494+
rbd -> assertThat(rbd.getBeanClass()).isEqualTo(beanClass));
495+
};
496+
}
497+
458498
@Test
459499
void processAheadOfTimeWhenHasCglibProxyUseProxy() {
460500
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
@@ -493,6 +533,20 @@ void processAheadOfTimeWhenHasCglibProxyAndMixedAutowiring() {
493533
});
494534
}
495535

536+
@Test
537+
void processAheadOfTimeWhenHasCglibProxyWithAnnotationsOnTheUserClasConstructor() {
538+
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
539+
applicationContext.registerBean("config", ValueCglibConfiguration.class);
540+
testCompiledResult(applicationContext, (initializer, compiled) -> {
541+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> {
542+
context.setEnvironment(new MockEnvironment().withProperty("name", "AOT World"));
543+
initializer.initialize(context);
544+
});
545+
assertThat(freshApplicationContext.getBean(ValueCglibConfiguration.class)
546+
.getName()).isEqualTo("AOT World");
547+
});
548+
}
549+
496550
@Test
497551
void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() {
498552
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
@@ -516,6 +570,10 @@ void processAheadOfTimeWhenHasCglibProxyWithArgumentsRegisterIntrospectionHintsO
516570
.accepts(generationContext.getRuntimeHints());
517571
}
518572

573+
private String toCglibClassSimpleName(Class<?> configClass) {
574+
return configClass.getSimpleName() + CGLIB_CONFIGURATION_CLASS_SUFFIX;
575+
}
576+
519577
}
520578

521579
@Nested
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.testfixture.context.annotation;
18+
19+
import org.springframework.beans.factory.annotation.Value;
20+
import org.springframework.context.annotation.Configuration;
21+
22+
@Configuration
23+
public class ValueCglibConfiguration {
24+
25+
private final String name;
26+
27+
public ValueCglibConfiguration(@Value("${name:World}") String name) {
28+
this.name = name;
29+
}
30+
31+
public String getName() {
32+
return this.name;
33+
}
34+
}

0 commit comments

Comments
 (0)