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

@ -43,6 +43,7 @@ import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -207,9 +208,24 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
Executable executable = this.lookup.get(registeredBean); Executable executable = this.lookup.get(registeredBean);
AutowiredArguments arguments = resolveArguments(registeredBean, executable); AutowiredArguments arguments = resolveArguments(registeredBean, executable);
if (this.generator != null) { 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<T> 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 @Nullable

View File

@ -109,8 +109,7 @@ class InstanceSupplierCodeGenerator {
String beanName = registeredBean.getBeanName(); String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass(); Class<?> beanClass = registeredBean.getBeanClass();
Class<?> declaringClass = ClassUtils Class<?> declaringClass = constructor.getDeclaringClass();
.getUserClass(constructor.getDeclaringClass());
boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass); boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass);
Visibility accessVisibility = getAccessVisibility(registeredBean, constructor); Visibility accessVisibility = getAccessVisibility(registeredBean, constructor);
if (accessVisibility != Visibility.PRIVATE) { if (accessVisibility != Visibility.PRIVATE) {

View File

@ -53,6 +53,14 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
return currentlyInvokedFactoryMethod.get(); 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 @Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {

View File

@ -73,7 +73,7 @@ import org.springframework.util.ReflectionUtils;
class ConfigurationClassEnhancer { class ConfigurationClassEnhancer {
// The callbacks to use. Note that these callbacks must be stateless. // 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 BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE NoOp.INSTANCE

View File

@ -18,6 +18,8 @@ package org.springframework.context.annotation;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -26,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.lang.model.element.Modifier; 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.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; 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.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 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.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 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.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationStartupAware; import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
@ -110,8 +119,8 @@ import org.springframework.util.CollectionUtils;
* @since 3.0 * @since 3.0
*/ */
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
BeanFactoryInitializationAotProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanRegistrationAotProcessor, BeanFactoryInitializationAotProcessor, PriorityOrdered,
BeanClassLoaderAware, EnvironmentAware { ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
/** /**
* A {@code BeanNameGenerator} using fully qualified class names as default bean names. * 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)); 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 @Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors); 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<String> 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;
}
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.event.EventListenerFactory; import org.springframework.context.event.EventListenerFactory;
import org.springframework.core.Conventions; import org.springframework.core.Conventions;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -42,23 +43,24 @@ import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* Utilities for identifying {@link Configuration} classes. * Utilities for identifying and configuring {@link Configuration} classes.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @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"); Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
private static final String ORDER_ATTRIBUTE = static final String ORDER_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order"); Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");
@ -73,6 +75,17 @@ abstract class ConfigurationClassUtils {
candidateIndicators.add(ImportResource.class.getName()); 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 * 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 * @param metadataReaderFactory the current factory in use by the caller
* @return whether the candidate qualifies as (any kind of) configuration class * @return whether the candidate qualifies as (any kind of) configuration class
*/ */
public static boolean checkConfigurationClassCandidate( static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName(); String className = beanDef.getBeanClassName();
@ -149,7 +162,7 @@ abstract class ConfigurationClassUtils {
* @return {@code true} if the given class is to be registered for * @return {@code true} if the given class is to be registered for
* configuration class processing; {@code false} otherwise * 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... // Do not consider an interface or an annotation...
if (metadata.isInterface()) { if (metadata.isInterface()) {
return false; return false;

View File

@ -36,14 +36,17 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 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.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanFactoryInitializationCode; 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.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext; 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.ImportAwareConfiguration;
import org.springframework.context.testfixture.context.generator.annotation.ImportConfiguration; 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.Ordered;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -78,7 +81,7 @@ class ConfigurationClassPostProcessorAotContributionTests {
@Test @Test
void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() { void processAheadOfTimeWhenNoImportAwareConfigurationReturnsNull() {
assertThat(getContribution(SimpleConfiguration.class)).isNull(); assertThat(getContribution(SimpleComponent.class)).isNull();
} }
@Test @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 @Nullable
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) { private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind; 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.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent; 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.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.InitDestroyComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredFieldComponent; import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredFieldComponent;
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredMethodComponent; 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.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader; 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.Compiled;
import org.springframework.core.test.tools.TestCompiler; import org.springframework.core.test.tools.TestCompiler;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -93,13 +97,13 @@ class ApplicationContextAotGeneratorTests {
GenericApplicationContext applicationContext = new GenericApplicationContext(); GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, applicationContext.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder BeanDefinitionBuilder
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class) .rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class)); applicationContext.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class));
applicationContext.registerBeanDefinition("number", applicationContext.registerBeanDefinition("number",
BeanDefinitionBuilder BeanDefinitionBuilder
.rootBeanDefinition(Integer.class, "valueOf") .rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition()); .addConstructorArgValue("42").getBeanDefinition());
testCompiledResult(applicationContext, (initializer, compiled) -> { testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number"); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number");
@ -200,8 +204,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition( applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class) .rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("initDestroyComponent", applicationContext.registerBeanDefinition("initDestroyComponent",
new RootBeanDefinition(InitDestroyComponent.class)); new RootBeanDefinition(InitDestroyComponent.class));
testCompiledResult(applicationContext, (initializer, compiled) -> { testCompiledResult(applicationContext, (initializer, compiled) -> {
@ -220,8 +224,8 @@ class ApplicationContextAotGeneratorTests {
applicationContext.registerBeanDefinition( applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class) .rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class); RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);
beanDefinition.setInitMethodName("customInit"); beanDefinition.setInitMethodName("customInit");
beanDefinition.setDestroyMethodName("customDestroy"); 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 @Test
void processAheadOfTimeWithPropertySource() { 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) { 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) { private static TestGenerationContext processAheadOfTime(GenericApplicationContext applicationContext) {
@ -307,23 +343,29 @@ class ApplicationContextAotGeneratorTests {
return generationContext; return generationContext;
} }
private void testCompiledResult(GenericApplicationContext applicationContext, private static void testCompiledResult(GenericApplicationContext applicationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) { BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
testCompiledResult(processAheadOfTime(applicationContext), result); testCompiledResult(processAheadOfTime(applicationContext), result);
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private void testCompiledResult(TestGenerationContext generationContext, private static void testCompiledResult(TestGenerationContext generationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) { BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
TestCompiler.forSystem().with(generationContext).compile(compiled -> TestCompiler.forSystem().with(generationContext).compile(compiled ->
result.accept(compiled.getInstance(ApplicationContextInitializer.class), 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) { ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext(); GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
initializer.initialize(freshApplicationContext); initializer.initialize(freshApplicationContext);
freshApplicationContext.refresh();
return freshApplicationContext; return freshApplicationContext;
} }

View File

@ -28,7 +28,7 @@ public class CglibConfiguration {
@Bean @Bean
public String prefix() { public String prefix() {
return "Hello" + counter.getAndIncrement(); return getPrefix() + counter.getAndIncrement();
} }
@Bean @Bean
@ -36,4 +36,8 @@ public class CglibConfiguration {
return prefix() + " World"; return prefix() + " World";
} }
protected String getPrefix() {
return "Hello";
}
} }

View File

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

View File

@ -31,7 +31,7 @@ import org.springframework.transaction.PlatformTransactionManager;
* @author Sam Brannen * @author Sam Brannen
* @since 4.1 * @since 4.1
*/ */
@Configuration(proxyBeanMethods = false) @Configuration
public class EmptyDatabaseConfig { public class EmptyDatabaseConfig {
@Bean @Bean