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:
Juergen Hoeller 2022-08-10 23:30:19 +02:00
parent 496b1879ab
commit b31a15851e
25 changed files with 364 additions and 173 deletions

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.
@ -22,6 +22,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
@ -33,7 +34,8 @@ import org.springframework.lang.Nullable;
* @since 3.2
*/
@SuppressWarnings("serial")
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor {
@Nullable
protected Advisor advisor;
@ -58,8 +60,27 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
public Class<?> determineBeanType(Class<?> beanClass, String beanName) {
if (this.advisor != null && isEligible(beanClass)) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
proxyFactory.setTargetClass(beanClass);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxyClass(classLoader);
}
return beanClass;
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 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.
@ -52,4 +52,13 @@ public interface AopProxy {
*/
Object getProxy(@Nullable ClassLoader classLoader);
/**
* Determine the proxy class.
* @param classLoader the class loader to create the proxy class with
* (or {@code null} for the low-level proxy facility's default)
* @return the proxy class
* @since 6.0
*/
Class<?> getProxyClass(@Nullable ClassLoader classLoader);
}

View File

@ -153,11 +153,20 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
public Object getProxy() {
return getProxy(null);
return buildProxy(null, false);
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
return buildProxy(classLoader, false);
}
@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return (Class<?>) buildProxy(classLoader, true);
}
private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
if (logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}
@ -190,6 +199,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
@ -203,7 +213,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
@ -216,6 +226,11 @@ class CglibAopProxy implements AopProxy, Serializable {
}
}
protected Class<?> createProxyClass(Enhancer enhancer) {
enhancer.setInterceptDuringConstruction(false);
return enhancer.createClass();
}
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
enhancer.setInterceptDuringConstruction(false);
enhancer.setCallbacks(callbacks);
@ -375,22 +390,6 @@ class CglibAopProxy implements AopProxy, Serializable {
return false;
}
/**
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
* to a plain reflection invocation in case of a fast-class generation failure.
*/
@Nullable
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
return methodProxy.invoke(target, args);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
}
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@ -424,10 +423,9 @@ class CglibAopProxy implements AopProxy, Serializable {
/**
* Method interceptor used for static targets with no advice chain. The call
* is passed directly back to the target. Used when the proxy needs to be
* exposed and it can't be determined that the method won't return
* {@code this}.
* Method interceptor used for static targets with no advice chain. The call is
* passed directly back to the target. Used when the proxy needs to be exposed
* and it can't be determined that the method won't return {@code this}.
*/
private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {
@ -441,7 +439,7 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object retVal = invokeMethod(this.target, method, args, methodProxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args);
return processReturnType(proxy, this.target, method, retVal);
}
}
@ -466,7 +464,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.target, method, args);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@ -494,7 +492,7 @@ class CglibAopProxy implements AopProxy, Serializable {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
Object retVal = invokeMethod(target, method, args, methodProxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
return processReturnType(proxy, target, method, retVal);
}
finally {
@ -524,7 +522,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = invokeMethod(target, method, args, methodProxy);
Object retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
return processReturnType(proxy, target, method, retVal);
}
finally {
@ -695,13 +693,13 @@ class CglibAopProxy implements AopProxy, Serializable {
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = invokeMethod(target, method, argsToUse, methodProxy);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
@ -743,17 +741,11 @@ class CglibAopProxy implements AopProxy, Serializable {
*/
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
@Nullable
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
Object[] arguments, @Nullable Class<?> targetClass,
List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
// Only use method proxy for public methods not derived from java.lang.Object
this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null);
}
@Override
@ -781,35 +773,6 @@ class CglibAopProxy implements AopProxy, Serializable {
}
}
}
/**
* Gives a marginal performance improvement versus using reflection to
* invoke the target when invoking public methods.
*/
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
try {
return this.methodProxy.invoke(this.target, this.arguments);
}
catch (CodeGenerationException ex) {
logFastClassGenerationFailure(this.method);
}
}
return super.invokeJoinpoint();
}
static boolean isMethodProxyCompatible(Method method) {
return (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method));
}
static void logFastClassGenerationFailure(Method method) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to generate CGLIB fast class for method: " + method);
}
}
}

View File

@ -20,7 +20,6 @@ import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
import org.springframework.util.ClassUtils;
/**
@ -63,9 +62,6 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
if (NativeDetector.inNativeImage()) {
throw new AopConfigException("Subclass-based proxies are not support yet in native images");
}
return new ObjenesisCglibAopProxy(config);
}
else {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@ -126,6 +126,12 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
@SuppressWarnings("deprecation")
@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return Proxy.getProxyClass(classLoader, this.proxiedInterfaces);
}
/**
* Finds any {@link #equals} or {@link #hashCode} method that may be defined
* on the supplied set of interfaces.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 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.
@ -52,6 +52,11 @@ class ObjenesisCglibAopProxy extends CglibAopProxy {
}
@Override
protected Class<?> createProxyClass(Enhancer enhancer) {
return enhancer.createClass();
}
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();

View File

@ -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.
@ -110,6 +110,17 @@ public class ProxyFactory extends ProxyCreatorSupport {
return createAopProxy().getProxy(classLoader);
}
/**
* Determine the proxy class according to the settings in this factory.
* @param classLoader the class loader to create the proxy class with
* (or {@code null} for the low-level proxy facility's default)
* @return the proxy class
* @since 6.0
*/
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxyClass(classLoader);
}
/**
* Create a new proxy for the given interface and interceptor.

View File

@ -38,6 +38,7 @@ import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ProxyProcessorSupport;
import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;
import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
@ -231,6 +232,30 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
return this.proxyTypes.get(cacheKey);
}
@Override
public Class<?> determineBeanType(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName);
Class<?> proxyType = this.proxyTypes.get(cacheKey);
if (proxyType == null) {
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
}
else {
targetSource = EmptyTargetSource.forClass(beanClass);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
proxyType = createProxyClass(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxyType);
}
}
return (proxyType != null ? proxyType : beanClass);
}
@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) {
@ -436,6 +461,18 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
}
private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);
}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
@ -477,7 +514,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
}
return proxyFactory.getProxy(classLoader);
return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}
/**

View File

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

View File

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

View File

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

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.
@ -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));

View File

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

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.
@ -422,6 +422,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
String filename = resource.getFilename();
if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
// Ignore CGLIB-generated classes in the classpath
continue;
}
if (traceEnabled) {
logger.trace("Scanning " + resource);
}

View File

@ -123,6 +123,7 @@ class ConfigurationClassEnhancer {
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
@ -508,6 +509,7 @@ class ConfigurationClassEnhancer {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(factoryBean.getClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setCallbackType(MethodInterceptor.class);
// Ideally create enhanced FactoryBean proxy without constructor side effects,

View File

@ -64,7 +64,6 @@ import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
import org.springframework.core.NativeDetector;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment;
@ -286,11 +285,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME)
? new AotContribution(beanFactory) : null);
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
return (beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME) ? new AotContribution(beanFactory) : null);
}
/**
@ -452,7 +448,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
configBeanDefs.put(beanName, abd);
}
}
if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
enhanceConfigClasses.end();
return;
@ -514,8 +510,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private static final String BEAN_FACTORY_VARIABLE = BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE;
private static final ParameterizedTypeName STRING_STRING_MAP = ParameterizedTypeName
.get(Map.class, String.class, String.class);
private static final ParameterizedTypeName STRING_STRING_MAP =
ParameterizedTypeName.get(Map.class, String.class, String.class);
private static final String MAPPINGS_VARIABLE = "mappings";
@ -523,15 +519,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private static final String BEAN_NAME = "org.springframework.context.annotation.internalImportAwareAotProcessor";
private final ConfigurableListableBeanFactory beanFactory;
public AotContribution(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode) {
@ -550,11 +543,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
}
private void generateAddPostProcessorMethod(MethodSpec.Builder method,
Map<String, String> mappings) {
method.addJavadoc(
"Add ImportAwareBeanPostProcessor to support ImportAware beans");
private void generateAddPostProcessorMethod(MethodSpec.Builder method, Map<String, String> mappings) {
method.addJavadoc("Add ImportAwareBeanPostProcessor to support ImportAware beans");
method.addModifiers(Modifier.PRIVATE);
method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_VARIABLE);
method.addCode(generateAddPostProcessorCode(mappings));

View File

@ -18,6 +18,7 @@ package org.springframework.context.support;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@ -29,6 +30,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
@ -408,8 +410,29 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
invokeBeanFactoryPostProcessors(this.beanFactory);
this.beanFactory.freezeConfiguration();
PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
preDetermineBeanTypes();
}
/**
* Pre-determine bean types in order to trigger early proxy class creation.
* @see org.springframework.beans.factory.BeanFactory#getType
* @see SmartInstantiationAwareBeanPostProcessor#determineBeanType
*/
private void preDetermineBeanTypes() {
List<SmartInstantiationAwareBeanPostProcessor> bpps =
PostProcessorRegistrationDelegate.loadBeanPostProcessors(
this.beanFactory, SmartInstantiationAwareBeanPostProcessor.class);
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType != null) {
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
beanType = bpp.determineBeanType(beanType, beanName);
}
}
}
}
//---------------------------------------------------------------------
// Convenient methods for registering individual beans
//---------------------------------------------------------------------

View File

@ -135,6 +135,7 @@ jar {
exclude "org/springframework/cglib/core/AsmApi*.class"
exclude "org/springframework/cglib/core/KeyFactory.class"
exclude "org/springframework/cglib/core/KeyFactory\$*.class"
exclude "org/springframework/cglib/core/MethodWrapper*.class"
exclude "org/springframework/cglib/core/ReflectUtils*.class"
exclude "org/springframework/cglib/proxy/Enhancer*.class"
exclude "org/springframework/cglib/proxy/MethodProxy*.class"

View File

@ -0,0 +1,47 @@
/*
* Copyright 2003,2004 The Apache Software Foundation
*
* 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.cglib.core;
import java.lang.reflect.Method;
import java.util.*;
@SuppressWarnings({"rawtypes", "unchecked"})
public class MethodWrapper {
// SPRING PATCH BEGIN
private record MethodWrapperKey(String name, List<String> parameterTypes, String returnType) {
}
// SPRING PATCH END
private MethodWrapper() {
}
public static Object create(Method method) {
// SPRING PATCH BEGIN
return new MethodWrapperKey(method.getName(),
Arrays.asList(ReflectUtils.getNames(method.getParameterTypes())),
method.getReturnType().getName());
// SPRING PATCH END
}
public static Set createSet(Collection methods) {
Set set = new HashSet();
for (Iterator it = methods.iterator(); it.hasNext();) {
set.add(create((Method)it.next()));
}
return set;
}
}

View File

@ -20,12 +20,16 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
@ -444,6 +448,15 @@ public class ReflectUtils {
Class c = null;
Throwable t = THROWABLE;
String generatedClasses = System.getProperty("cglib.generatedClasses");
if (generatedClasses != null) {
Path path = Path.of(generatedClasses + "/" + className.replace(".", "/") + ".class");
Files.createDirectories(path.getParent());
try (OutputStream os = Files.newOutputStream(path)) {
new ByteArrayInputStream(b).transferTo(os);
}
}
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader) {
try {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 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.
@ -17,23 +17,36 @@
package org.springframework.cglib.core;
/**
* Custom extension of CGLIB's {@link DefaultNamingPolicy}, modifying
* the tag in generated class names from "ByCGLIB" to "BySpringCGLIB".
* Custom variant of CGLIB's {@link DefaultNamingPolicy}, modifying the tag
* in generated class names from "EnhancerByCGLIB" etc to a "SpringCGLIB" tag
* and using a plain counter suffix instead of a hash code suffix (as of 6.0).
*
* <p>This is primarily designed to avoid clashes between a regular CGLIB
* version (used by some other library) and Spring's embedded variant,
* in case the same class happens to get proxied for different purposes.
* <p>This allows for reliably discovering pre-generated Spring proxy classes
* in the classpath (as written at runtime when the "cglib.generatedClasses"
* system property points to a specific directory to store the proxy classes).
*
* @author Juergen Hoeller
* @since 3.2.8
* @since 3.2.8 / 6.0
*/
public class SpringNamingPolicy extends DefaultNamingPolicy {
public final class SpringNamingPolicy implements NamingPolicy {
public static final SpringNamingPolicy INSTANCE = new SpringNamingPolicy();
@Override
protected String getTag() {
return "BySpringCGLIB";
private SpringNamingPolicy() {
}
public String getClassName(String prefix, String source, Object key, Predicate names) {
if (prefix == null) {
prefix = "org.springframework.cglib.empty.Object";
} else if (prefix.startsWith("java")) {
prefix = "_" + prefix;
}
String base = prefix + "$$SpringCGLIB$$";
int index = 0;
String attempt = base + index;
while (names.evaluate(attempt))
attempt = base + index++;
return attempt;
}
}

View File

@ -100,9 +100,6 @@ public class Enhancer extends AbstractClassGenerator {
private static final Source SOURCE = new Source(Enhancer.class.getName());
private static final EnhancerKey KEY_FACTORY =
(EnhancerKey) KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null);
private static final String BOUND_FIELD = "CGLIB$BOUND";
private static final String FACTORY_DATA_FIELD = "CGLIB$FACTORY_DATA";
@ -197,19 +194,16 @@ public class Enhancer extends AbstractClassGenerator {
private Object currentKey;
/**
* Internal interface, only public due to ClassLoader issues.
*/
public interface EnhancerKey {
public Object newInstance(String type,
String[] interfaces,
// SPRING PATCH BEGIN
private record EnhancerKey(String type,
List<String> interfaces,
WeakCacheKey<CallbackFilter> filter,
Type[] callbackTypes,
List<Type> callbackTypes,
boolean useFactory,
boolean interceptDuringConstruction,
Long serialVersionUID);
Long serialVersionUID) {
}
// SPRING PATCH END
private Class[] interfaces;
@ -561,13 +555,15 @@ public class Enhancer extends AbstractClassGenerator {
private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
// SPRING PATCH BEGIN
Object key = new EnhancerKey((superclass != null) ? superclass.getName() : null,
(interfaces != null ? Arrays.asList(ReflectUtils.getNames(interfaces)) :null),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
Arrays.asList(callbackTypes),
useFactory,
interceptDuringConstruction,
serialVersionUID);
// SPRING PATCH END
this.currentKey = key;
Object result = super.create(key);
return result;

View File

@ -660,6 +660,7 @@ public class ResolvableMethod {
enhancer.setSuperclass(type);
enhancer.setInterfaces(new Class<?>[] {Supplier.class});
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
Class<?> proxyClass = enhancer.createClass();

View File

@ -659,6 +659,7 @@ public class ResolvableMethod {
enhancer.setSuperclass(type);
enhancer.setInterfaces(new Class<?>[] {Supplier.class});
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
Class<?> proxyClass = enhancer.createClass();

View File

@ -786,6 +786,7 @@ public class MvcUriComponentsBuilder {
enhancer.setSuperclass(controllerType);
enhancer.setInterfaces(new Class<?>[] {MethodInvocationInfo.class});
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
Class<?> proxyClass = enhancer.createClass();