Enable cglib proxy for configuration classes if necessary

This commit updates code generation to customize the instantiation of
a configuration class that requires a proxy. Rather than instantiating
the raw class, the proxy is used.

Closes gh-29107
This commit is contained in:
Stephane Nicoll
2022-09-13 12:49:03 +02:00
parent f2e9d112b1
commit 2f20d6322b
11 changed files with 259 additions and 45 deletions

View File

@@ -36,14 +36,17 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanFactoryInitializationCode;
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ImportAwareConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ImportConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.SimpleConfiguration;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
@@ -78,7 +81,7 @@ class ConfigurationClassPostProcessorAotContributionTests {
@Test
void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() {
assertThat(getContribution(SimpleConfiguration.class)).isNull();
assertThat(getContribution(SimpleComponent.class)).isNull();
}
@Test
@@ -323,6 +326,34 @@ class ConfigurationClassPostProcessorAotContributionTests {
}
@Nested
class ConfigurationClassProxyTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
private final ConfigurationClassPostProcessor processor = new ConfigurationClassPostProcessor();
@Test
void processAheadOfTimeRegularConfigurationClass() {
assertThat(this.processor.processAheadOfTime(
getRegisteredBean(SimpleConfiguration.class))).isNull();
}
@Test
void processAheadOfTimeFullConfigurationClass() {
assertThat(this.processor.processAheadOfTime(
getRegisteredBean(CglibConfiguration.class))).isNotNull();
}
private RegisteredBean getRegisteredBean(Class<?> bean) {
this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(bean));
this.processor.postProcessBeanFactory(this.beanFactory);
return RegisteredBean.of(this.beanFactory, "test");
}
}
@Nullable
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

View File

@@ -22,6 +22,7 @@ import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind;
@@ -54,6 +55,7 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent;
import org.springframework.context.testfixture.context.generator.annotation.CglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ConfigurableCglibConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredFieldComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredMethodComponent;
@@ -64,8 +66,10 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
@@ -93,13 +97,13 @@ class ApplicationContextAotGeneratorTests {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class));
applicationContext.registerBeanDefinition("number",
BeanDefinitionBuilder
.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number");
@@ -200,8 +204,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("initDestroyComponent",
new RootBeanDefinition(InitDestroyComponent.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
@@ -220,8 +224,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);
beanDefinition.setInitMethodName("customInit");
beanDefinition.setDestroyMethodName("customDestroy");
@@ -270,17 +274,6 @@ class ApplicationContextAotGeneratorTests {
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
String proxyClassName = CglibConfiguration.class.getName() + "$$SpringCGLIB$$0";
assertThat(context.getGeneratedFiles()
.getGeneratedFileContent(Kind.CLASS, proxyClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(proxyClassName))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}
@Test
void processAheadOfTimeWithPropertySource() {
@@ -295,8 +288,51 @@ class ApplicationContextAotGeneratorTests {
});
}
@Nested
@CompileWithForkedClassLoader
class ConfigurationClassCglibProxy {
@Test
void processAheadOfTimeWhenHasCglibProxyWriteProxyAndGenerateReflectionHints() throws IOException {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
TestGenerationContext context = processAheadOfTime(applicationContext);
String proxyClassName = CglibConfiguration.class.getName() + "$$SpringCGLIB$$0";
assertThat(context.getGeneratedFiles()
.getGeneratedFileContent(Kind.CLASS, proxyClassName.replace('.', '/') + ".class")).isNotNull();
assertThat(RuntimeHintsPredicates.reflection().onType(TypeReference.of(proxyClassName))
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(context.getRuntimeHints());
}
@Test
void processAheadOfTimeWhenHasCglibProxyUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(CglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBean("prefix", String.class)).isEqualTo("Hello0");
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hello0 World");
});
}
@Test
void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(ConfigurableCglibConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer);
freshApplicationContext.setEnvironment(new MockEnvironment().withProperty("test.prefix", "Hi"));
freshApplicationContext.refresh();
assertThat(freshApplicationContext.getBean("prefix", String.class)).isEqualTo("Hi0");
assertThat(freshApplicationContext.getBean("text", String.class)).isEqualTo("Hi0 World");
});
}
}
private Consumer<List<? extends JdkProxyHint>> doesNotHaveProxyFor(Class<?> target) {
return hints -> assertThat(hints).noneMatch(hint -> hint.getProxiedInterfaces().get(0).equals(target));
return hints -> assertThat(hints).noneMatch(hint ->
hint.getProxiedInterfaces().get(0).equals(TypeReference.of(target)));
}
private static TestGenerationContext processAheadOfTime(GenericApplicationContext applicationContext) {
@@ -307,23 +343,29 @@ class ApplicationContextAotGeneratorTests {
return generationContext;
}
private void testCompiledResult(GenericApplicationContext applicationContext,
private static void testCompiledResult(GenericApplicationContext applicationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
testCompiledResult(processAheadOfTime(applicationContext), result);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void testCompiledResult(TestGenerationContext generationContext,
private static void testCompiledResult(TestGenerationContext generationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
TestCompiler.forSystem().with(generationContext).compile(compiled ->
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
}
private GenericApplicationContext toFreshApplicationContext(
private static GenericApplicationContext toFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer);
freshApplicationContext.refresh();
return freshApplicationContext;
}
private static GenericApplicationContext createFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
initializer.initialize(freshApplicationContext);
freshApplicationContext.refresh();
return freshApplicationContext;
}