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

@@ -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

View File

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