From 2f20d6322b7a0fcbbaa80280849e7c31fc78d4a9 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 13 Sep 2022 12:49:03 +0200 Subject: [PATCH] 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 --- .../factory/aot/BeanInstanceSupplier.java | 20 ++++- .../aot/InstanceSupplierCodeGenerator.java | 3 +- .../support/SimpleInstantiationStrategy.java | 8 ++ .../ConfigurationClassEnhancer.java | 2 +- .../ConfigurationClassPostProcessor.java | 69 +++++++++++++- .../annotation/ConfigurationClassUtils.java | 33 ++++--- ...lassPostProcessorAotContributionTests.java | 35 +++++++- .../ApplicationContextAotGeneratorTests.java | 90 ++++++++++++++----- .../annotation/CglibConfiguration.java | 6 +- .../ConfigurableCglibConfiguration.java | 36 ++++++++ .../context/jdbc/EmptyDatabaseConfig.java | 2 +- 11 files changed, 259 insertions(+), 45 deletions(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/ConfigurableCglibConfiguration.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index 13eee5161cd..dd4d4867a3d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -43,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionValueResolver; import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.support.SimpleInstantiationStrategy; import org.springframework.core.CollectionFactory; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; @@ -207,9 +208,24 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl Executable executable = this.lookup.get(registeredBean); AutowiredArguments arguments = resolveArguments(registeredBean, executable); if (this.generator != null) { - return this.generator.apply(registeredBean, arguments); + return invokeBeanSupplier(executable, () -> + this.generator.apply(registeredBean, arguments)); + } + return invokeBeanSupplier(executable, () -> + instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray())); + } + + private T invokeBeanSupplier(Executable executable, ThrowingSupplier beanSupplier) { + if (!(executable instanceof Method)) { + return beanSupplier.get(); + } + try { + SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod((Method) executable); + return beanSupplier.get(); + } + finally { + SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(null); } - return instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray()); } @Nullable diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index 0e8e39de3db..fb1db09997f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -109,8 +109,7 @@ class InstanceSupplierCodeGenerator { String beanName = registeredBean.getBeanName(); Class beanClass = registeredBean.getBeanClass(); - Class declaringClass = ClassUtils - .getUserClass(constructor.getDeclaringClass()); + Class declaringClass = constructor.getDeclaringClass(); boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass); Visibility accessVisibility = getAccessVisibility(registeredBean, constructor); if (accessVisibility != Visibility.PRIVATE) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index ee7b0593806..aa3e0df58ea 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -53,6 +53,14 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { return currentlyInvokedFactoryMethod.get(); } + /** + * Set the factory method currently being invoked or {@code null} to reset. + * @param method the factory method currently being invoked or {@code null} + */ + public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) { + currentlyInvokedFactoryMethod.set(method); + } + @Override public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 69c3d1ce956..28cd494c27e 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -73,7 +73,7 @@ import org.springframework.util.ReflectionUtils; class ConfigurationClassEnhancer { // The callbacks to use. Note that these callbacks must be stateless. - private static final Callback[] CALLBACKS = new Callback[] { + static final Callback[] CALLBACKS = new Callback[] { new BeanMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index e2a8a53ad40..7cdd6f1db14 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -18,6 +18,8 @@ package org.springframework.context.annotation; import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -26,6 +28,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.function.Supplier; import javax.lang.model.element.Modifier; @@ -46,6 +49,11 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; +import org.springframework.beans.factory.aot.BeanRegistrationCode; +import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments; +import org.springframework.beans.factory.aot.BeanRegistrationCodeFragmentsDecorator; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -61,6 +69,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationStartupAware; import org.springframework.context.EnvironmentAware; @@ -110,8 +119,8 @@ import org.springframework.util.CollectionUtils; * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, - BeanFactoryInitializationAotProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, - BeanClassLoaderAware, EnvironmentAware { + BeanRegistrationAotProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered, + ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware { /** * A {@code BeanNameGenerator} using fully qualified class names as default bean names. @@ -294,6 +303,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); } + @Nullable + @Override + public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + Object configClassAttr = registeredBean.getMergedBeanDefinition() + .getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE); + if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { + Class proxyClass = registeredBean.getBeanType().toClass(); + return BeanRegistrationAotContribution.withCustomCodeFragments(codeFragments -> + new ConfigurationClassProxyBeanRegistrationCodeFragments(codeFragments, proxyClass)); + } + return null; + } + @Override public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors); @@ -692,4 +714,47 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } + private static class ConfigurationClassProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator { + + private final Class proxyClass; + + public ConfigurationClassProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments, + Class proxyClass) { + super(codeFragments); + this.proxyClass = proxyClass; + } + + @Override + public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext, + BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate attributeFilter) { + CodeBlock.Builder code = CodeBlock.builder(); + code.add(super.generateSetBeanDefinitionPropertiesCode(generationContext, + beanRegistrationCode, beanDefinition, attributeFilter)); + code.addStatement("$T.initializeConfigurationClass($T.class)", + ConfigurationClassUtils.class, ClassUtils.getUserClass(this.proxyClass)); + return code.build(); + } + + @Override + public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, + BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod, + boolean allowDirectSupplierShortcut) { + return super.generateInstanceSupplierCode(generationContext, beanRegistrationCode, + proxyExecutable(constructorOrFactoryMethod), allowDirectSupplierShortcut); + } + + private Executable proxyExecutable(Executable rawClassExecutable) { + if (rawClassExecutable instanceof Constructor) { + try { + return this.proxyClass.getConstructor(rawClassExecutable.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex); + } + } + return rawClassExecutable; + } + + } + } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index da377b13fd3..f16c0404046 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -30,6 +30,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.cglib.proxy.Enhancer; import org.springframework.context.event.EventListenerFactory; import org.springframework.core.Conventions; import org.springframework.core.Ordered; @@ -42,23 +43,24 @@ import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; /** - * Utilities for identifying {@link Configuration} classes. + * Utilities for identifying and configuring {@link Configuration} classes. * * @author Chris Beams * @author Juergen Hoeller * @author Sam Brannen - * @since 3.1 + * @author Stephane Nicoll + * @since 6.0 */ -abstract class ConfigurationClassUtils { +public abstract class ConfigurationClassUtils { - public static final String CONFIGURATION_CLASS_FULL = "full"; + static final String CONFIGURATION_CLASS_FULL = "full"; - public static final String CONFIGURATION_CLASS_LITE = "lite"; + static final String CONFIGURATION_CLASS_LITE = "lite"; - public static final String CONFIGURATION_CLASS_ATTRIBUTE = + static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); - private static final String ORDER_ATTRIBUTE = + static final String ORDER_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order"); @@ -73,6 +75,17 @@ abstract class ConfigurationClassUtils { candidateIndicators.add(ImportResource.class.getName()); } + /** + * Initialize a configuration class proxy for the specified class. + * @param userClass the configuration class to initialize + */ + @SuppressWarnings("unused") // Used by AOT-optimized generated code + public static Class initializeConfigurationClass(Class userClass) { + Class configurationClass = new ConfigurationClassEnhancer().enhance(userClass, null); + Enhancer.registerStaticCallbacks(configurationClass, ConfigurationClassEnhancer.CALLBACKS); + return configurationClass; + } + /** * Check whether the given bean definition is a candidate for a configuration class @@ -82,7 +95,7 @@ abstract class ConfigurationClassUtils { * @param metadataReaderFactory the current factory in use by the caller * @return whether the candidate qualifies as (any kind of) configuration class */ - public static boolean checkConfigurationClassCandidate( + static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { String className = beanDef.getBeanClassName(); @@ -149,7 +162,7 @@ abstract class ConfigurationClassUtils { * @return {@code true} if the given class is to be registered for * configuration class processing; {@code false} otherwise */ - public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { + static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java index 55c6b6ab7c3..94e61d7e639 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests.java @@ -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(); 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 75ed5b2162d..9706686a6a0 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 @@ -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> 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, Compiled> result) { testCompiledResult(processAheadOfTime(applicationContext), result); } @SuppressWarnings({ "rawtypes", "unchecked" }) - private void testCompiledResult(TestGenerationContext generationContext, + private static void testCompiledResult(TestGenerationContext generationContext, BiConsumer, Compiled> result) { TestCompiler.forSystem().with(generationContext).compile(compiled -> result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled)); } - private GenericApplicationContext toFreshApplicationContext( + private static GenericApplicationContext toFreshApplicationContext( + ApplicationContextInitializer initializer) { + GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer); + freshApplicationContext.refresh(); + return freshApplicationContext; + } + + private static GenericApplicationContext createFreshApplicationContext( ApplicationContextInitializer initializer) { GenericApplicationContext freshApplicationContext = new GenericApplicationContext(); initializer.initialize(freshApplicationContext); - freshApplicationContext.refresh(); return freshApplicationContext; } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/CglibConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/CglibConfiguration.java index dd33cb40cee..8bd3a225346 100644 --- a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/CglibConfiguration.java +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/CglibConfiguration.java @@ -28,7 +28,7 @@ public class CglibConfiguration { @Bean public String prefix() { - return "Hello" + counter.getAndIncrement(); + return getPrefix() + counter.getAndIncrement(); } @Bean @@ -36,4 +36,8 @@ public class CglibConfiguration { return prefix() + " World"; } + protected String getPrefix() { + return "Hello"; + } + } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/ConfigurableCglibConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/ConfigurableCglibConfiguration.java new file mode 100644 index 00000000000..eefe88f9fad --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/generator/annotation/ConfigurableCglibConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2022 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.generator.annotation; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +public class ConfigurableCglibConfiguration extends CglibConfiguration { + + private final Environment environment; + + public ConfigurableCglibConfiguration(Environment environment) { + this.environment = environment; + } + + @Override + protected String getPrefix() { + return this.environment.getProperty("test.prefix", String.class, "Howdy"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java index 123d7976580..6a36e322f95 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/EmptyDatabaseConfig.java @@ -31,7 +31,7 @@ import org.springframework.transaction.PlatformTransactionManager; * @author Sam Brannen * @since 4.1 */ -@Configuration(proxyBeanMethods = false) +@Configuration public class EmptyDatabaseConfig { @Bean