Support for pre-generated CGLIB proxy classes (in AOT scenarios)
Includes runtime storing of generated classes to a directory specified by the "cglib.generatedClasses" system property. Avoids lazy CGLIB fast-class generation and replaces generated Enhancer and MethodWrapper key classes with equivalent record types. Introduces support for early type determination in InstantiationStrategy, AopProxy and SmartInstantiationAwareBeanPostProcessor - in order to trigger CGLIB class generation in refreshForAotProcessing (through early determineBeanType calls for bean definitions). Closes gh-28115
This commit is contained in:
@@ -69,6 +69,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.support.LookupOverride;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
@@ -311,44 +312,26 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeanCreationException {
|
||||
checkLookupMethods(beanClass, beanName);
|
||||
|
||||
// Pick up subclass with fresh lookup method override from above
|
||||
if (this.beanFactory instanceof AbstractAutowireCapableBeanFactory aacbf) {
|
||||
RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
|
||||
if (mbd.getFactoryMethodName() == null && mbd.hasBeanClass()) {
|
||||
return aacbf.getInstantiationStrategy().getActualBeanClass(mbd, beanName, this.beanFactory);
|
||||
}
|
||||
}
|
||||
return beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
|
||||
throws BeanCreationException {
|
||||
|
||||
// Let's check for lookup methods here...
|
||||
if (!this.lookupMethodsChecked.contains(beanName)) {
|
||||
if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {
|
||||
try {
|
||||
Class<?> targetClass = beanClass;
|
||||
do {
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
Lookup lookup = method.getAnnotation(Lookup.class);
|
||||
if (lookup != null) {
|
||||
Assert.state(this.beanFactory != null, "No BeanFactory available");
|
||||
LookupOverride override = new LookupOverride(method, lookup.value());
|
||||
try {
|
||||
RootBeanDefinition mbd = (RootBeanDefinition)
|
||||
this.beanFactory.getMergedBeanDefinition(beanName);
|
||||
mbd.getMethodOverrides().addOverride(override);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
throw new BeanCreationException(beanName,
|
||||
"Cannot apply @Lookup to beans without corresponding bean definition");
|
||||
}
|
||||
}
|
||||
});
|
||||
targetClass = targetClass.getSuperclass();
|
||||
}
|
||||
while (targetClass != null && targetClass != Object.class);
|
||||
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
|
||||
}
|
||||
}
|
||||
this.lookupMethodsChecked.add(beanName);
|
||||
}
|
||||
checkLookupMethods(beanClass, beanName);
|
||||
|
||||
// Quick check on the concurrent map first, with minimal locking.
|
||||
Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
|
||||
@@ -450,6 +433,41 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
return (candidateConstructors.length > 0 ? candidateConstructors : null);
|
||||
}
|
||||
|
||||
private void checkLookupMethods(Class<?> beanClass, final String beanName) throws BeanCreationException {
|
||||
if (!this.lookupMethodsChecked.contains(beanName)) {
|
||||
if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {
|
||||
try {
|
||||
Class<?> targetClass = beanClass;
|
||||
do {
|
||||
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
|
||||
Lookup lookup = method.getAnnotation(Lookup.class);
|
||||
if (lookup != null) {
|
||||
Assert.state(this.beanFactory != null, "No BeanFactory available");
|
||||
LookupOverride override = new LookupOverride(method, lookup.value());
|
||||
try {
|
||||
RootBeanDefinition mbd = (RootBeanDefinition)
|
||||
this.beanFactory.getMergedBeanDefinition(beanName);
|
||||
mbd.getMethodOverrides().addOverride(override);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
throw new BeanCreationException(beanName,
|
||||
"Cannot apply @Lookup to beans without corresponding bean definition");
|
||||
}
|
||||
}
|
||||
});
|
||||
targetClass = targetClass.getSuperclass();
|
||||
}
|
||||
while (targetClass != null && targetClass != Object.class);
|
||||
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
|
||||
}
|
||||
}
|
||||
this.lookupMethodsChecked.add(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
|
||||
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -28,12 +28,10 @@ import org.springframework.lang.Nullable;
|
||||
* <p><b>NOTE:</b> This interface is a special purpose interface, mainly for
|
||||
* internal use within the framework. In general, application-provided
|
||||
* post-processors should simply implement the plain {@link BeanPostProcessor}
|
||||
* interface or derive from the {@link InstantiationAwareBeanPostProcessorAdapter}
|
||||
* class. New methods might be added to this interface even in point releases.
|
||||
* interface.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0.3
|
||||
* @see InstantiationAwareBeanPostProcessorAdapter
|
||||
*/
|
||||
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
|
||||
|
||||
@@ -41,6 +39,8 @@ public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationA
|
||||
* Predict the type of the bean to be eventually returned from this
|
||||
* processor's {@link #postProcessBeforeInstantiation} callback.
|
||||
* <p>The default implementation returns {@code null}.
|
||||
* Specific implementations should try to predict the bean type as
|
||||
* far as known/cached already, without extra processing steps.
|
||||
* @param beanClass the raw class of the bean
|
||||
* @param beanName the name of the bean
|
||||
* @return the type of the bean, or {@code null} if not predictable
|
||||
@@ -51,6 +51,22 @@ public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationA
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type of the bean to be eventually returned from this
|
||||
* processor's {@link #postProcessBeforeInstantiation} callback.
|
||||
* <p>The default implementation returns the given bean class as-is.
|
||||
* Specific implementations should fully evaluate their processing steps
|
||||
* in order to create/initialize a potential proxy class upfront.
|
||||
* @param beanClass the raw class of the bean
|
||||
* @param beanName the name of the bean
|
||||
* @return the type of the bean (never {@code null})
|
||||
* @throws org.springframework.beans.BeansException in case of errors
|
||||
* @since 6.0
|
||||
*/
|
||||
default Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeansException {
|
||||
return beanClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the candidate constructors to use for the given bean.
|
||||
* <p>The default implementation returns {@code null}.
|
||||
|
||||
@@ -70,7 +70,6 @@ import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -174,12 +173,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
ignoreDependencyInterface(BeanNameAware.class);
|
||||
ignoreDependencyInterface(BeanFactoryAware.class);
|
||||
ignoreDependencyInterface(BeanClassLoaderAware.class);
|
||||
if (NativeDetector.inNativeImage()) {
|
||||
this.instantiationStrategy = new SimpleInstantiationStrategy();
|
||||
}
|
||||
else {
|
||||
this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
|
||||
}
|
||||
this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +198,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
/**
|
||||
* Return the instantiation strategy to use for creating bean instances.
|
||||
*/
|
||||
protected InstantiationStrategy getInstantiationStrategy() {
|
||||
public InstantiationStrategy getInstantiationStrategy() {
|
||||
return this.instantiationStrategy;
|
||||
}
|
||||
|
||||
@@ -222,7 +216,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
* names if needed.
|
||||
*/
|
||||
@Nullable
|
||||
protected ParameterNameDiscoverer getParameterNameDiscoverer() {
|
||||
public ParameterNameDiscoverer getParameterNameDiscoverer() {
|
||||
return this.parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
@@ -683,9 +677,15 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
protected Class<?> determineTargetType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
|
||||
Class<?> targetType = mbd.getTargetType();
|
||||
if (targetType == null) {
|
||||
targetType = (mbd.getFactoryMethodName() != null ?
|
||||
getTypeForFactoryMethod(beanName, mbd, typesToMatch) :
|
||||
resolveBeanClass(mbd, beanName, typesToMatch));
|
||||
if (mbd.getFactoryMethodName() != null) {
|
||||
targetType = getTypeForFactoryMethod(beanName, mbd, typesToMatch);
|
||||
}
|
||||
else {
|
||||
targetType = resolveBeanClass(mbd, beanName, typesToMatch);
|
||||
if (mbd.hasBeanClass()) {
|
||||
targetType = getInstantiationStrategy().getActualBeanClass(mbd, beanName, this);
|
||||
}
|
||||
}
|
||||
if (ObjectUtils.isEmpty(typesToMatch) || getTempClassLoader() == null) {
|
||||
mbd.resolvedTargetType = targetType;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -81,10 +81,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
@Nullable Constructor<?> ctor, Object... args) {
|
||||
|
||||
// Must generate CGLIB subclass...
|
||||
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
|
||||
if (!bd.hasMethodOverrides()) {
|
||||
return super.getActualBeanClass(bd, beanName, owner);
|
||||
}
|
||||
return new CglibSubclassCreator(bd, owner).createEnhancedSubclass(bd);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An inner class created for historical reasons to avoid external CGLIB dependency
|
||||
@@ -142,10 +149,11 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
* Create an enhanced subclass of the bean class for the provided bean
|
||||
* definition, using CGLIB.
|
||||
*/
|
||||
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
|
||||
public Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(beanDefinition.getBeanClass());
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
if (this.owner instanceof ConfigurableBeanFactory) {
|
||||
ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();
|
||||
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -83,4 +83,12 @@ public interface InstantiationStrategy {
|
||||
@Nullable Object factoryBean, Method factoryMethod, Object... args)
|
||||
throws BeansException;
|
||||
|
||||
/**
|
||||
* Determine the actual class for the given bean definition, as instantiated at runtime.
|
||||
* @since 6.0
|
||||
*/
|
||||
default Class<?> getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
|
||||
return bd.getBeanClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user