From 0d7247774272146ed3a2812f89875623bf57e851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Fri, 6 Dec 2024 15:34:00 +0100 Subject: [PATCH] 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 --- .../DefaultBeanRegistrationCodeFragments.java | 2 +- .../ApplicationContextAotGeneratorTests.java | 60 ++++++++++++++++++- .../annotation/ValueCglibConfiguration.java | 34 +++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/ValueCglibConfiguration.java 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 c34331f853..498b04633b 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 277ea2952c..b2da6f1c7b 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 0000000000..506e99e56a --- /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; + } +}